From cb904dac19cf269380c491ced0e73fcbcd5e597e Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 4 Mar 2026 17:28:54 +0700 Subject: [PATCH 01/30] feat(dpp): shielded state transitions and Orchard bundle types (Medusa) Add the DPP layer for the shielded credit pool: - Five new state transition types: Shield (15), ShieldedTransfer (16), Unshield (17), ShieldFromAssetLock (18), ShieldedWithdrawal (19) - Orchard bundle builder (shielded/builder.rs) with platform sighash - Bundle serialization (SerializedAction) and deserialization - 12 new consensus errors (basic + state) for shielded validation - PlatformAddress Orchard payment address support - Platform version fields for shielded serialization and validation - Workspace profile optimizations for ZK crypto crates Co-Authored-By: Claude Opus 4.6 --- Cargo.toml | 29 + packages/rs-dpp/Cargo.toml | 5 + .../src/address_funds/platform_address.rs | 345 +++++++++- packages/rs-dpp/src/asset_lock/mod.rs | 1 + .../src/errors/consensus/basic/basic_error.rs | 29 +- .../consensus/basic/state_transition/mod.rs | 14 + .../shielded_empty_proof_error.rs | 37 ++ .../shielded_invalid_value_balance_error.rs | 41 ++ .../shielded_no_actions_error.rs | 37 ++ .../shielded_too_many_actions_error.rs | 46 ++ .../shielded_zero_anchor_error.rs | 37 ++ .../unshield_amount_zero_error.rs | 37 ++ ...shield_value_balance_below_amount_error.rs | 44 ++ packages/rs-dpp/src/errors/consensus/codes.rs | 15 + .../rs-dpp/src/errors/consensus/state/mod.rs | 1 + .../shielded/insufficient_pool_notes_error.rs | 46 ++ .../insufficient_shielded_fee_error.rs | 36 ++ .../state/shielded/invalid_anchor_error.rs | 39 ++ .../shielded/invalid_shielded_proof_error.rs | 36 ++ .../errors/consensus/state/shielded/mod.rs | 11 + .../shielded/nullifier_already_spent_error.rs | 36 ++ .../src/errors/consensus/state/state_error.rs | 20 + packages/rs-dpp/src/lib.rs | 1 + packages/rs-dpp/src/shielded/builder.rs | 611 ++++++++++++++++++ packages/rs-dpp/src/shielded/mod.rs | 170 +++++ packages/rs-dpp/src/state_transition/mod.rs | 171 ++++- .../src/state_transition/proof_result.rs | 12 + .../state_transition_types.rs | 5 + .../state_transition/state_transitions/mod.rs | 3 + .../shielded/common_validation.rs | 51 ++ .../state_transitions/shielded/mod.rs | 6 + .../methods/mod.rs | 61 ++ .../methods/v0/mod.rs | 31 + .../shield_from_asset_lock_transition/mod.rs | 61 ++ .../proved.rs | 27 + ...ate_transition_estimated_fee_validation.rs | 22 + .../state_transition_like.rs | 75 +++ .../state_transition_validation.rs | 15 + .../v0/mod.rs | 107 +++ .../v0/proved.rs | 18 + .../v0/state_transition_like.rs | 63 ++ .../v0/state_transition_validation.rs | 50 ++ .../v0/types.rs | 16 + .../v0/v0_methods.rs | 53 ++ .../v0/version.rs | 9 + .../version.rs | 11 + .../shielded/shield_transition/methods/mod.rs | 66 ++ .../shield_transition/methods/v0/mod.rs | 43 ++ .../shielded/shield_transition/mod.rs | 63 ++ ...ate_transition_estimated_fee_validation.rs | 20 + .../state_transition_like.rs | 96 +++ .../state_transition_validation.rs | 19 + .../shielded/shield_transition/v0/mod.rs | 113 ++++ .../v0/state_transition_like.rs | 89 +++ .../v0/state_transition_validation.rs | 122 ++++ .../shielded/shield_transition/v0/types.rs | 16 + .../shield_transition/v0/v0_methods.rs | 66 ++ .../shielded/shield_transition/v0/version.rs | 9 + .../shielded/shield_transition/version.rs | 11 + .../accessors/mod.rs | 14 + .../accessors/v0/mod.rs | 15 + .../methods/mod.rs | 51 ++ .../methods/v0/mod.rs | 25 + .../shielded_transfer_transition/mod.rs | 64 ++ ...ate_transition_estimated_fee_validation.rs | 16 + .../state_transition_like.rs | 41 ++ .../state_transition_validation.rs | 15 + .../shielded_transfer_transition/v0/mod.rs | 86 +++ .../v0/state_transition_like.rs | 51 ++ .../v0/state_transition_validation.rs | 50 ++ .../shielded_transfer_transition/v0/types.rs | 16 + .../v0/v0_methods.rs | 31 + .../v0/version.rs | 9 + .../shielded_transfer_transition/version.rs | 11 + .../accessors/mod.rs | 21 + .../accessors/v0/mod.rs | 19 + .../methods/mod.rs | 63 ++ .../methods/v0/mod.rs | 34 + .../shielded_withdrawal_transition/mod.rs | 64 ++ ...ate_transition_estimated_fee_validation.rs | 16 + .../state_transition_like.rs | 41 ++ .../state_transition_validation.rs | 15 + .../shielded_withdrawal_transition/v0/mod.rs | 100 +++ .../v0/state_transition_like.rs | 50 ++ .../v0/state_transition_validation.rs | 69 ++ .../v0/types.rs | 16 + .../v0/v0_methods.rs | 43 ++ .../v0/version.rs | 9 + .../shielded_withdrawal_transition/version.rs | 11 + .../unshield_transition/accessors/mod.rs | 21 + .../unshield_transition/accessors/v0/mod.rs | 19 + .../unshield_transition/methods/mod.rs | 55 ++ .../unshield_transition/methods/v0/mod.rs | 30 + .../shielded/unshield_transition/mod.rs | 64 ++ ...ate_transition_estimated_fee_validation.rs | 16 + .../state_transition_like.rs | 41 ++ .../state_transition_validation.rs | 15 + .../shielded/unshield_transition/v0/mod.rs | 96 +++ .../v0/state_transition_like.rs | 50 ++ .../v0/state_transition_validation.rs | 69 ++ .../shielded/unshield_transition/v0/types.rs | 16 + .../unshield_transition/v0/v0_methods.rs | 37 ++ .../unshield_transition/v0/version.rs | 9 + .../shielded/unshield_transition/version.rs | 11 + packages/rs-platform-version/Cargo.toml | 2 +- .../mod.rs | 5 + .../v1.rs | 25 + .../v2.rs | 25 + .../drive_abci_method_versions/mod.rs | 3 + .../drive_abci_method_versions/v1.rs | 3 + .../drive_abci_method_versions/v2.rs | 3 + .../drive_abci_method_versions/v3.rs | 3 + .../drive_abci_method_versions/v4.rs | 3 + .../drive_abci_method_versions/v5.rs | 3 + .../drive_abci_method_versions/v6.rs | 3 + .../drive_abci_method_versions/v7.rs | 3 + .../drive_abci_query_versions/mod.rs | 16 + .../drive_abci_query_versions/v1.rs | 49 +- .../drive_abci_validation_versions/mod.rs | 20 + .../drive_abci_validation_versions/v1.rs | 46 ++ .../drive_abci_validation_versions/v2.rs | 46 ++ .../drive_abci_validation_versions/v3.rs | 46 ++ .../drive_abci_validation_versions/v4.rs | 46 ++ .../drive_abci_validation_versions/v5.rs | 46 ++ .../drive_abci_validation_versions/v6.rs | 46 ++ .../drive_abci_validation_versions/v7.rs | 46 ++ .../drive_abci_validation_versions/v8.rs | 46 ++ .../drive_group_method_versions/mod.rs | 8 + .../drive_grove_method_versions/mod.rs | 3 + .../drive_grove_method_versions/v1.rs | 3 + .../mod.rs | 5 + .../v1.rs | 5 + .../v2.rs | 5 + .../drive_verify_method_versions/mod.rs | 13 + .../drive_verify_method_versions/v1.rs | 15 +- .../src/version/drive_versions/mod.rs | 14 +- .../src/version/drive_versions/v1.rs | 13 + .../src/version/drive_versions/v2.rs | 13 + .../src/version/drive_versions/v3.rs | 13 + .../src/version/drive_versions/v4.rs | 13 + .../src/version/drive_versions/v5.rs | 13 + .../src/version/drive_versions/v6.rs | 13 + .../src/version/drive_versions/v7.rs | 131 ++++ .../feature_initial_protocol_versions.rs | 1 + .../src/version/fee/hashing/mod.rs | 2 + .../src/version/fee/hashing/v1.rs | 1 + .../src/version/mocks/v2_test.rs | 59 +- .../src/version/mocks/v3_test.rs | 3 + .../src/version/system_limits/mod.rs | 1 + .../src/version/system_limits/v1.rs | 1 + .../rs-platform-version/src/version/v12.rs | 4 +- 151 files changed, 5806 insertions(+), 19 deletions(-) create mode 100644 packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_empty_proof_error.rs create mode 100644 packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_invalid_value_balance_error.rs create mode 100644 packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_no_actions_error.rs create mode 100644 packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_too_many_actions_error.rs create mode 100644 packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_zero_anchor_error.rs create mode 100644 packages/rs-dpp/src/errors/consensus/basic/state_transition/unshield_amount_zero_error.rs create mode 100644 packages/rs-dpp/src/errors/consensus/basic/state_transition/unshield_value_balance_below_amount_error.rs create mode 100644 packages/rs-dpp/src/errors/consensus/state/shielded/insufficient_pool_notes_error.rs create mode 100644 packages/rs-dpp/src/errors/consensus/state/shielded/insufficient_shielded_fee_error.rs create mode 100644 packages/rs-dpp/src/errors/consensus/state/shielded/invalid_anchor_error.rs create mode 100644 packages/rs-dpp/src/errors/consensus/state/shielded/invalid_shielded_proof_error.rs create mode 100644 packages/rs-dpp/src/errors/consensus/state/shielded/mod.rs create mode 100644 packages/rs-dpp/src/errors/consensus/state/shielded/nullifier_already_spent_error.rs create mode 100644 packages/rs-dpp/src/shielded/builder.rs create mode 100644 packages/rs-dpp/src/shielded/mod.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/common_validation.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/mod.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/mod.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/v0/mod.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/mod.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/proved.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/state_transition_estimated_fee_validation.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/state_transition_like.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/state_transition_validation.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/proved.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/state_transition_like.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/state_transition_validation.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/types.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/v0_methods.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/version.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/version.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/methods/mod.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/methods/v0/mod.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/mod.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/state_transition_estimated_fee_validation.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/state_transition_like.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/state_transition_validation.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/mod.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/state_transition_like.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/state_transition_validation.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/types.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/v0_methods.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/version.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/version.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/accessors/mod.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/accessors/v0/mod.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/methods/mod.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/methods/v0/mod.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/mod.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/state_transition_estimated_fee_validation.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/state_transition_like.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/state_transition_validation.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/mod.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/state_transition_like.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/state_transition_validation.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/types.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/v0_methods.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/version.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/version.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/accessors/mod.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/accessors/v0/mod.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/mod.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/v0/mod.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/mod.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/state_transition_estimated_fee_validation.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/state_transition_like.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/state_transition_validation.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/state_transition_like.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/state_transition_validation.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/types.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/v0_methods.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/version.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/version.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/accessors/mod.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/accessors/v0/mod.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/mod.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/v0/mod.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/mod.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/state_transition_estimated_fee_validation.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/state_transition_like.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/state_transition_validation.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/state_transition_like.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/state_transition_validation.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/types.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/v0_methods.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/version.rs create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/version.rs create mode 100644 packages/rs-platform-version/src/version/drive_versions/v7.rs diff --git a/Cargo.toml b/Cargo.toml index 1080580a12f..a32bdea8c26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,35 @@ key-wallet = { git = "https://github.com/dashpay/rust-dashcore", rev = "0bc6592b key-wallet-manager = { git = "https://github.com/dashpay/rust-dashcore", rev = "0bc6592bd41037ffc532d813d4c0828bea7cf882" } dashcore-rpc = { git = "https://github.com/dashpay/rust-dashcore", rev = "0bc6592bd41037ffc532d813d4c0828bea7cf882" } +# Optimize heavy crypto crates even in dev/test builds so that +# Halo 2 proof generation and verification run at near-release speed. +# Without this, ZK operations are 10-100x slower (debug field arithmetic). +[profile.dev.package.halo2_proofs] +opt-level = 3 +[profile.dev.package.halo2_gadgets] +opt-level = 3 +[profile.dev.package.halo2_poseidon] +opt-level = 3 +[profile.dev.package.orchard] +opt-level = 3 +[profile.dev.package.pasta_curves] +opt-level = 3 +[profile.dev.package.grovedb-commitment-tree] +opt-level = 3 + +[profile.test.package.halo2_proofs] +opt-level = 3 +[profile.test.package.halo2_gadgets] +opt-level = 3 +[profile.test.package.halo2_poseidon] +opt-level = 3 +[profile.test.package.orchard] +opt-level = 3 +[profile.test.package.pasta_curves] +opt-level = 3 +[profile.test.package.grovedb-commitment-tree] +opt-level = 3 + [workspace.package] version = "3.1.0-dev.1" diff --git a/packages/rs-dpp/Cargo.toml b/packages/rs-dpp/Cargo.toml index 09643a4a23b..0e439cb121f 100644 --- a/packages/rs-dpp/Cargo.toml +++ b/packages/rs-dpp/Cargo.toml @@ -71,6 +71,7 @@ strum = { version = "0.26", features = ["derive"] } json-schema-compatibility-validator = { path = '../rs-json-schema-compatibility-validator', optional = true } once_cell = "1.19.0" tracing = { version = "0.1.41" } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "7ecb8465fad750c7cddd5332adb6f97fcceb498b", optional = true } [dev-dependencies] tokio = { version = "1.40", features = ["full"] } @@ -327,5 +328,9 @@ extended-document = [ ] token-reward-explanations = ["dep:chrono-tz"] +shielded-bundle-building = [ + "state-transition-signing", + "dep:grovedb-commitment-tree", +] factories = [] client = ["factories", "state-transitions"] diff --git a/packages/rs-dpp/src/address_funds/platform_address.rs b/packages/rs-dpp/src/address_funds/platform_address.rs index 8d6f3e7a722..5121f016e05 100644 --- a/packages/rs-dpp/src/address_funds/platform_address.rs +++ b/packages/rs-dpp/src/address_funds/platform_address.rs @@ -555,6 +555,204 @@ impl PlatformAddress { } } +// --------------------------------------------------------------------------- +// Orchard shielded payment address (requires `shielded-bundle-building`) +// --------------------------------------------------------------------------- + +/// Size of the Orchard diversifier (11 bytes). +#[cfg(feature = "shielded-bundle-building")] +pub const ORCHARD_DIVERSIFIER_SIZE: usize = 11; +/// Size of the Orchard diversified transmission key pk_d (32 bytes, Pallas curve point). +#[cfg(feature = "shielded-bundle-building")] +pub const ORCHARD_PKD_SIZE: usize = 32; +/// Total size of a raw Orchard payment address (43 bytes = diversifier + pk_d). +#[cfg(feature = "shielded-bundle-building")] +pub const ORCHARD_ADDRESS_SIZE: usize = ORCHARD_DIVERSIFIER_SIZE + ORCHARD_PKD_SIZE; + +/// An Orchard shielded payment address. +/// +/// Composed of a diversifier (11 bytes) and a diversified transmission key (32 bytes). +/// The diversifier enables a single spending key to derive an unlimited number of +/// unlinkable payment addresses. Only the holder of the corresponding FullViewingKey +/// (or IncomingViewingKey) can link diversified addresses to the same wallet. +/// +/// Bech32m encoding uses type byte `0x10`, producing addresses that start with `z`: +/// - Mainnet: `dash1z...` +/// - Testnet: `tdash1z...` +/// +/// The raw Orchard address format matches Zcash Orchard (43 bytes), but the +/// string encoding is Dash-specific (no F4Jumble, no Unified Address wrapper). +/// +/// Use [`From`] to convert from the `orchard` crate's native type, +/// or [`to_payment_address()`](OrchardAddress::to_payment_address) to convert back +/// (with pk_d validation). +/// +/// Requires the `shielded-bundle-building` feature. +#[cfg(feature = "shielded-bundle-building")] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct OrchardAddress { + /// 11-byte diversifier derived from the FullViewingKey with an index. + diversifier: [u8; ORCHARD_DIVERSIFIER_SIZE], + /// 32-byte diversified transmission key (point on the Pallas curve). + pk_d: [u8; ORCHARD_PKD_SIZE], +} + +#[cfg(feature = "shielded-bundle-building")] +impl OrchardAddress { + /// Type byte for Orchard addresses in bech32m encoding (user-facing). + /// Produces 'z' as the first bech32 character. + pub const ORCHARD_TYPE: u8 = 0x10; + + /// Creates an OrchardAddress from its raw components. + pub fn from_parts( + diversifier: [u8; ORCHARD_DIVERSIFIER_SIZE], + pk_d: [u8; ORCHARD_PKD_SIZE], + ) -> Self { + Self { diversifier, pk_d } + } + + /// Creates an OrchardAddress from a 43-byte raw address. + /// + /// The first 11 bytes are the diversifier, the next 32 are pk_d. + /// No validation is performed on pk_d; use [`From`] + /// for a pre-validated address. + pub fn from_raw_bytes(bytes: &[u8; ORCHARD_ADDRESS_SIZE]) -> Self { + let mut diversifier = [0u8; ORCHARD_DIVERSIFIER_SIZE]; + let mut pk_d = [0u8; ORCHARD_PKD_SIZE]; + diversifier.copy_from_slice(&bytes[..ORCHARD_DIVERSIFIER_SIZE]); + pk_d.copy_from_slice(&bytes[ORCHARD_DIVERSIFIER_SIZE..]); + Self { diversifier, pk_d } + } + + /// Returns the raw 43-byte address (diversifier || pk_d). + pub fn to_raw_bytes(&self) -> [u8; ORCHARD_ADDRESS_SIZE] { + let mut bytes = [0u8; ORCHARD_ADDRESS_SIZE]; + bytes[..ORCHARD_DIVERSIFIER_SIZE].copy_from_slice(&self.diversifier); + bytes[ORCHARD_DIVERSIFIER_SIZE..].copy_from_slice(&self.pk_d); + bytes + } + + /// Returns the 11-byte diversifier. + pub fn diversifier(&self) -> &[u8; ORCHARD_DIVERSIFIER_SIZE] { + &self.diversifier + } + + /// Returns the 32-byte diversified transmission key. + pub fn pk_d(&self) -> &[u8; ORCHARD_PKD_SIZE] { + &self.pk_d + } + + /// Encodes the OrchardAddress as a bech32m string for the specified network. + /// + /// Format: `1` + /// - Data: type_byte (0x10) || diversifier (11 bytes) || pk_d (32 bytes) + /// - Total payload: 44 bytes + /// - Checksum: bech32m (BIP-350) + /// + /// # Example + /// ```ignore + /// let address = OrchardAddress::from_raw_bytes(&raw_bytes); + /// let encoded = address.to_bech32m_string(Network::Dash); + /// // Returns something like "dash1z..." + /// ``` + pub fn to_bech32m_string(&self, network: Network) -> String { + let hrp_str = PlatformAddress::hrp_for_network(network); + let hrp = Hrp::parse(hrp_str).expect("HRP is valid"); + + let mut payload = Vec::with_capacity(1 + ORCHARD_ADDRESS_SIZE); + payload.push(Self::ORCHARD_TYPE); + payload.extend_from_slice(&self.diversifier); + payload.extend_from_slice(&self.pk_d); + + bech32::encode::(hrp, &payload).expect("encoding should succeed") + } + + /// Converts this address to an Orchard [`PaymentAddress`](grovedb_commitment_tree::PaymentAddress). + /// + /// Returns an error if `pk_d` is not a valid Pallas curve point. + pub fn to_payment_address( + &self, + ) -> Result { + crate::shielded::builder::orchard_address_to_payment_address(self) + } + + /// Decodes a bech32m-encoded Orchard address string. + /// + /// # Returns + /// - `Ok((OrchardAddress, Network))` - The decoded address and its network + /// - `Err(ProtocolError)` - If the address is invalid + pub fn from_bech32m_string(s: &str) -> Result<(Self, Network), ProtocolError> { + let (hrp, data) = + bech32::decode(s).map_err(|e| ProtocolError::DecodingError(format!("{}", e)))?; + + let hrp_lower = hrp.as_str().to_ascii_lowercase(); + let network = match hrp_lower.as_str() { + s if s == PLATFORM_HRP_MAINNET => Network::Dash, + s if s == PLATFORM_HRP_TESTNET => Network::Testnet, + _ => { + return Err(ProtocolError::DecodingError(format!( + "invalid HRP '{}': expected '{}' or '{}'", + hrp, PLATFORM_HRP_MAINNET, PLATFORM_HRP_TESTNET + ))) + } + }; + + // Validate payload: 1 type byte + 11 diversifier + 32 pk_d = 44 bytes + if data.len() != 1 + ORCHARD_ADDRESS_SIZE { + return Err(ProtocolError::DecodingError(format!( + "invalid Orchard address length: expected {} bytes, got {}", + 1 + ORCHARD_ADDRESS_SIZE, + data.len() + ))); + } + + if data[0] != Self::ORCHARD_TYPE { + return Err(ProtocolError::DecodingError(format!( + "invalid Orchard address type byte: expected 0x{:02x}, got 0x{:02x}", + Self::ORCHARD_TYPE, + data[0] + ))); + } + + let mut diversifier = [0u8; ORCHARD_DIVERSIFIER_SIZE]; + let mut pk_d = [0u8; ORCHARD_PKD_SIZE]; + diversifier.copy_from_slice(&data[1..1 + ORCHARD_DIVERSIFIER_SIZE]); + pk_d.copy_from_slice(&data[1 + ORCHARD_DIVERSIFIER_SIZE..]); + + Ok((Self { diversifier, pk_d }, network)) + } +} + +/// Infallible conversion from the orchard crate's `PaymentAddress` to `OrchardAddress`. +/// +/// Extracts the raw 43 bytes (diversifier || pk_d) from the validated address. +#[cfg(feature = "shielded-bundle-building")] +impl From for OrchardAddress { + fn from(addr: grovedb_commitment_tree::PaymentAddress) -> Self { + Self::from_raw_bytes(&addr.to_raw_address_bytes()) + } +} + +/// Infallible conversion from a reference to `PaymentAddress`. +#[cfg(feature = "shielded-bundle-building")] +impl From<&grovedb_commitment_tree::PaymentAddress> for OrchardAddress { + fn from(addr: &grovedb_commitment_tree::PaymentAddress) -> Self { + Self::from_raw_bytes(&addr.to_raw_address_bytes()) + } +} + +#[cfg(feature = "shielded-bundle-building")] +impl std::fmt::Display for OrchardAddress { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Orchard(d={}, pk_d={})", + hex::encode(self.diversifier), + hex::encode(self.pk_d) + ) + } +} + impl std::fmt::Display for PlatformAddress { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -1303,7 +1501,7 @@ mod tests { assert_eq!(p2pkh.to_bytes()[0], 0x00); assert_eq!(p2sh.to_bytes()[0], 0x01); - // Bech32m encoding uses 0xb0/0x80 (verified by successful roundtrip) + // Bech32m encoding uses 0xb0/0xb8 (verified by successful roundtrip) let p2pkh_encoded = p2pkh.to_bech32m_string(Network::Dash); let p2sh_encoded = p2sh.to_bech32m_string(Network::Dash); @@ -1313,4 +1511,149 @@ mod tests { assert_eq!(p2pkh_decoded, p2pkh); assert_eq!(p2sh_decoded, p2sh); } + + // ======================== + // Orchard address tests (require shielded-bundle-building feature) + // ======================== + + #[cfg(feature = "shielded-bundle-building")] + #[test] + fn test_orchard_address_from_parts_roundtrip() { + let diversifier = [ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, + ]; + let pk_d = [0xAB; 32]; + let address = OrchardAddress::from_parts(diversifier, pk_d); + + assert_eq!(address.diversifier(), &diversifier); + assert_eq!(address.pk_d(), &pk_d); + + let raw = address.to_raw_bytes(); + assert_eq!(raw.len(), 43); + assert_eq!(&raw[..11], &diversifier); + assert_eq!(&raw[11..], &pk_d[..]); + + let recovered = OrchardAddress::from_raw_bytes(&raw); + assert_eq!(recovered, address); + } + + #[cfg(feature = "shielded-bundle-building")] + #[test] + fn test_orchard_bech32m_mainnet_roundtrip() { + let diversifier = [ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, + ]; + let pk_d = [0xAB; 32]; + let address = OrchardAddress::from_parts(diversifier, pk_d); + + let encoded = address.to_bech32m_string(Network::Dash); + assert!( + encoded.starts_with("dash1z"), + "Orchard mainnet address should start with 'dash1z', got: {}", + encoded + ); + + let (decoded, network) = + OrchardAddress::from_bech32m_string(&encoded).expect("decoding should succeed"); + assert_eq!(decoded, address); + assert_eq!(network, Network::Dash); + } + + #[cfg(feature = "shielded-bundle-building")] + #[test] + fn test_orchard_bech32m_testnet_roundtrip() { + let diversifier = [0xFF; 11]; + let pk_d = [0x42; 32]; + let address = OrchardAddress::from_parts(diversifier, pk_d); + + let encoded = address.to_bech32m_string(Network::Testnet); + assert!( + encoded.starts_with("tdash1z"), + "Orchard testnet address should start with 'tdash1z', got: {}", + encoded + ); + + let (decoded, network) = + OrchardAddress::from_bech32m_string(&encoded).expect("decoding should succeed"); + assert_eq!(decoded, address); + assert_eq!(network, Network::Testnet); + } + + #[cfg(feature = "shielded-bundle-building")] + #[test] + fn test_orchard_bech32m_wrong_type_byte_fails() { + // Manually construct an address with P2PKH type byte (0xb0) but 44-byte payload + let hrp = Hrp::parse("dash").unwrap(); + let mut payload = vec![PlatformAddress::P2PKH_TYPE]; // Wrong type byte + payload.extend_from_slice(&[0u8; 43]); + let encoded = bech32::encode::(hrp, &payload).unwrap(); + + let result = OrchardAddress::from_bech32m_string(&encoded); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("invalid Orchard address type byte")); + } + + #[cfg(feature = "shielded-bundle-building")] + #[test] + fn test_orchard_bech32m_wrong_length_fails() { + // Too short (only 20 bytes instead of 43) + let hrp = Hrp::parse("dash").unwrap(); + let mut payload = vec![OrchardAddress::ORCHARD_TYPE]; + payload.extend_from_slice(&[0u8; 20]); + let encoded = bech32::encode::(hrp, &payload).unwrap(); + + let result = OrchardAddress::from_bech32m_string(&encoded); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("invalid Orchard address length")); + } + + #[cfg(feature = "shielded-bundle-building")] + #[test] + fn test_orchard_and_platform_addresses_are_distinguishable() { + // Verify that the type bytes produce distinct prefixes + let p2pkh = PlatformAddress::P2pkh([0xAB; 20]); + let p2sh = PlatformAddress::P2sh([0xAB; 20]); + let orchard = OrchardAddress::from_parts([0xAB; 11], [0xAB; 32]); + + let p2pkh_enc = p2pkh.to_bech32m_string(Network::Dash); + let p2sh_enc = p2sh.to_bech32m_string(Network::Dash); + let orchard_enc = orchard.to_bech32m_string(Network::Dash); + + // All three start with "dash1" but have different type-byte characters + assert!(p2pkh_enc.starts_with("dash1k"), "P2PKH: {}", p2pkh_enc); + assert!(p2sh_enc.starts_with("dash1s"), "P2SH: {}", p2sh_enc); + assert!( + orchard_enc.starts_with("dash1z"), + "Orchard: {}", + orchard_enc + ); + + // Cross-decoding should fail + assert!(PlatformAddress::from_bech32m_string(&orchard_enc).is_err()); + assert!(OrchardAddress::from_bech32m_string(&p2pkh_enc).is_err()); + } + + #[cfg(feature = "shielded-bundle-building")] + #[test] + fn test_orchard_address_all_zeros() { + let address = OrchardAddress::from_parts([0u8; 11], [0u8; 32]); + let encoded = address.to_bech32m_string(Network::Dash); + let (decoded, _) = OrchardAddress::from_bech32m_string(&encoded).unwrap(); + assert_eq!(decoded, address); + } + + #[cfg(feature = "shielded-bundle-building")] + #[test] + fn test_orchard_address_display() { + let address = OrchardAddress::from_parts([0x01; 11], [0x02; 32]); + let display = format!("{}", address); + assert!(display.starts_with("Orchard(d=")); + assert!(display.contains("pk_d=")); + } } diff --git a/packages/rs-dpp/src/asset_lock/mod.rs b/packages/rs-dpp/src/asset_lock/mod.rs index 5133348df5b..3844bfc5933 100644 --- a/packages/rs-dpp/src/asset_lock/mod.rs +++ b/packages/rs-dpp/src/asset_lock/mod.rs @@ -6,6 +6,7 @@ pub type PastAssetLockStateTransitionHashes = Vec>; /// An enumeration of the possible states when querying platform to get the stored state of an outpoint /// representing if the asset lock was already used or not. +#[derive(Debug)] pub enum StoredAssetLockInfo { /// The asset lock was fully consumed in the past FullyConsumed, 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 db5d518eb93..be18afa8680 100644 --- a/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs +++ b/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs @@ -77,9 +77,11 @@ use crate::consensus::basic::state_transition::{ InputWitnessCountMismatchError, InputsNotLessThanOutputsError, InsufficientFundingAmountError, InvalidRemainderOutputCountError, InvalidStateTransitionTypeError, MissingStateTransitionTypeError, OutputAddressAlsoInputError, OutputBelowMinimumError, - OutputsNotGreaterThanInputsError, StateTransitionMaxSizeExceededError, - StateTransitionNotActiveError, TransitionNoInputsError, TransitionNoOutputsError, - TransitionOverMaxInputsError, TransitionOverMaxOutputsError, WithdrawalBalanceMismatchError, + OutputsNotGreaterThanInputsError, ShieldedEmptyProofError, ShieldedInvalidValueBalanceError, + ShieldedNoActionsError, ShieldedTooManyActionsError, ShieldedZeroAnchorError, + StateTransitionMaxSizeExceededError, StateTransitionNotActiveError, TransitionNoInputsError, + TransitionNoOutputsError, TransitionOverMaxInputsError, TransitionOverMaxOutputsError, + UnshieldAmountZeroError, UnshieldValueBalanceBelowAmountError, WithdrawalBalanceMismatchError, WithdrawalBelowMinAmountError, }; use crate::consensus::basic::{ @@ -657,6 +659,27 @@ pub enum BasicError { #[error(transparent)] OutputAddressAlsoInputError(OutputAddressAlsoInputError), + + #[error(transparent)] + ShieldedNoActionsError(ShieldedNoActionsError), + + #[error(transparent)] + ShieldedTooManyActionsError(ShieldedTooManyActionsError), + + #[error(transparent)] + ShieldedEmptyProofError(ShieldedEmptyProofError), + + #[error(transparent)] + ShieldedZeroAnchorError(ShieldedZeroAnchorError), + + #[error(transparent)] + ShieldedInvalidValueBalanceError(ShieldedInvalidValueBalanceError), + + #[error(transparent)] + UnshieldAmountZeroError(UnshieldAmountZeroError), + + #[error(transparent)] + UnshieldValueBalanceBelowAmountError(UnshieldValueBalanceBelowAmountError), } 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 a7c02e2db76..a5721bcdd4a 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 @@ -13,12 +13,19 @@ mod missing_state_transition_type_error; 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_invalid_value_balance_error; +mod shielded_no_actions_error; +mod shielded_too_many_actions_error; +mod shielded_zero_anchor_error; mod state_transition_max_size_exceeded_error; mod state_transition_not_active_error; mod transition_no_inputs_error; mod transition_no_outputs_error; mod transition_over_max_inputs_error; mod transition_over_max_outputs_error; +mod unshield_amount_zero_error; +mod unshield_value_balance_below_amount_error; mod withdrawal_balance_mismatch_error; mod withdrawal_below_min_amount_error; @@ -37,11 +44,18 @@ pub use missing_state_transition_type_error::*; 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_invalid_value_balance_error::*; +pub use shielded_no_actions_error::*; +pub use shielded_too_many_actions_error::*; +pub use shielded_zero_anchor_error::*; pub use state_transition_max_size_exceeded_error::*; pub use state_transition_not_active_error::*; pub use transition_no_inputs_error::*; pub use transition_no_outputs_error::*; pub use transition_over_max_inputs_error::*; pub use transition_over_max_outputs_error::*; +pub use unshield_amount_zero_error::*; +pub use unshield_value_balance_below_amount_error::*; pub use withdrawal_balance_mismatch_error::*; pub use withdrawal_below_min_amount_error::*; diff --git a/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_empty_proof_error.rs b/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_empty_proof_error.rs new file mode 100644 index 00000000000..93204cdd3bf --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_empty_proof_error.rs @@ -0,0 +1,37 @@ +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 transition proof must not be empty")] +#[platform_serialize(unversioned)] +pub struct ShieldedEmptyProofError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ +} + +impl ShieldedEmptyProofError { + pub fn new() -> Self { + Self {} + } +} + +impl Default for ShieldedEmptyProofError { + fn default() -> Self { + Self::new() + } +} + +impl From for ConsensusError { + fn from(err: ShieldedEmptyProofError) -> Self { + Self::BasicError(BasicError::ShieldedEmptyProofError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_invalid_value_balance_error.rs b/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_invalid_value_balance_error.rs new file mode 100644 index 00000000000..77cc5176e88 --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_invalid_value_balance_error.rs @@ -0,0 +1,41 @@ +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("Invalid shielded value_balance: {message}")] +#[platform_serialize(unversioned)] +pub struct ShieldedInvalidValueBalanceError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ + /// A human-readable description of the validation failure (e.g. "must be negative", + /// "must be positive", "must be zero"). Ideally this would be typed fields such as + /// `value_balance: i64` and `reason: &'static str`, but because this struct derives + /// `PlatformSerialize`/`Encode` and is part of the consensus protocol (error code 10822), + /// changing the field layout would break wire compatibility. Use a new version instead. + message: String, +} + +impl ShieldedInvalidValueBalanceError { + pub fn new(message: String) -> Self { + Self { message } + } + + pub fn message(&self) -> &str { + &self.message + } +} + +impl From for ConsensusError { + fn from(err: ShieldedInvalidValueBalanceError) -> Self { + Self::BasicError(BasicError::ShieldedInvalidValueBalanceError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_no_actions_error.rs b/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_no_actions_error.rs new file mode 100644 index 00000000000..c1ebe75b5f7 --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_no_actions_error.rs @@ -0,0 +1,37 @@ +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 transition must have at least one action")] +#[platform_serialize(unversioned)] +pub struct ShieldedNoActionsError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ +} + +impl ShieldedNoActionsError { + pub fn new() -> Self { + Self {} + } +} + +impl Default for ShieldedNoActionsError { + fn default() -> Self { + Self::new() + } +} + +impl From for ConsensusError { + fn from(err: ShieldedNoActionsError) -> Self { + Self::BasicError(BasicError::ShieldedNoActionsError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_too_many_actions_error.rs b/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_too_many_actions_error.rs new file mode 100644 index 00000000000..7b7218a79fd --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_too_many_actions_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 transition has {actual_actions} actions, which exceeds the maximum allowed {max_actions}" +)] +#[platform_serialize(unversioned)] +pub struct ShieldedTooManyActionsError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ + actual_actions: u16, + max_actions: u16, +} + +impl ShieldedTooManyActionsError { + pub fn new(actual_actions: u16, max_actions: u16) -> Self { + Self { + actual_actions, + max_actions, + } + } + + pub fn actual_actions(&self) -> u16 { + self.actual_actions + } + + pub fn max_actions(&self) -> u16 { + self.max_actions + } +} + +impl From for ConsensusError { + fn from(err: ShieldedTooManyActionsError) -> Self { + Self::BasicError(BasicError::ShieldedTooManyActionsError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_zero_anchor_error.rs b/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_zero_anchor_error.rs new file mode 100644 index 00000000000..10761119e9c --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_zero_anchor_error.rs @@ -0,0 +1,37 @@ +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 transition anchor must not be all zeros")] +#[platform_serialize(unversioned)] +pub struct ShieldedZeroAnchorError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ +} + +impl ShieldedZeroAnchorError { + pub fn new() -> Self { + Self {} + } +} + +impl Default for ShieldedZeroAnchorError { + fn default() -> Self { + Self::new() + } +} + +impl From for ConsensusError { + fn from(err: ShieldedZeroAnchorError) -> Self { + Self::BasicError(BasicError::ShieldedZeroAnchorError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/basic/state_transition/unshield_amount_zero_error.rs b/packages/rs-dpp/src/errors/consensus/basic/state_transition/unshield_amount_zero_error.rs new file mode 100644 index 00000000000..4d0a618c73f --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/basic/state_transition/unshield_amount_zero_error.rs @@ -0,0 +1,37 @@ +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("Unshield transition amount must be greater than zero")] +#[platform_serialize(unversioned)] +pub struct UnshieldAmountZeroError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ +} + +impl UnshieldAmountZeroError { + pub fn new() -> Self { + Self {} + } +} + +impl Default for UnshieldAmountZeroError { + fn default() -> Self { + Self::new() + } +} + +impl From for ConsensusError { + fn from(err: UnshieldAmountZeroError) -> Self { + Self::BasicError(BasicError::UnshieldAmountZeroError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/basic/state_transition/unshield_value_balance_below_amount_error.rs b/packages/rs-dpp/src/errors/consensus/basic/state_transition/unshield_value_balance_below_amount_error.rs new file mode 100644 index 00000000000..bbaae67bfb6 --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/basic/state_transition/unshield_value_balance_below_amount_error.rs @@ -0,0 +1,44 @@ +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("value_balance ({value_balance}) must be >= amount ({amount})")] +#[platform_serialize(unversioned)] +pub struct UnshieldValueBalanceBelowAmountError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ + value_balance: i64, + amount: u64, +} + +impl UnshieldValueBalanceBelowAmountError { + pub fn new(value_balance: i64, amount: u64) -> Self { + Self { + value_balance, + amount, + } + } + + pub fn value_balance(&self) -> i64 { + self.value_balance + } + + pub fn amount(&self) -> u64 { + self.amount + } +} + +impl From for ConsensusError { + fn from(err: UnshieldValueBalanceBelowAmountError) -> Self { + Self::BasicError(BasicError::UnshieldValueBalanceBelowAmountError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/codes.rs b/packages/rs-dpp/src/errors/consensus/codes.rs index 1758f022ac8..dff4f34aaa0 100644 --- a/packages/rs-dpp/src/errors/consensus/codes.rs +++ b/packages/rs-dpp/src/errors/consensus/codes.rs @@ -232,6 +232,14 @@ impl ErrorWithCode for BasicError { Self::OutputAddressAlsoInputError(_) => 10816, Self::InvalidRemainderOutputCountError(_) => 10817, Self::WithdrawalBelowMinAmountError(_) => 10818, + // Shielded transition errors (10819-10825) + Self::ShieldedNoActionsError(_) => 10819, + Self::ShieldedEmptyProofError(_) => 10820, + Self::ShieldedZeroAnchorError(_) => 10821, + Self::ShieldedInvalidValueBalanceError(_) => 10822, + Self::UnshieldAmountZeroError(_) => 10823, + Self::UnshieldValueBalanceBelowAmountError(_) => 10824, + Self::ShieldedTooManyActionsError(_) => 10825, } } } @@ -370,6 +378,13 @@ impl ErrorWithCode for StateError { Self::GroupActionAlreadyCompletedError(_) => 40802, Self::GroupActionAlreadySignedByIdentityError(_) => 40803, Self::ModificationOfGroupActionMainParametersNotPermittedError(_) => 40804, + + // Shielded errors: 40900-40999 + Self::InvalidAnchorError(_) => 40900, + Self::NullifierAlreadySpentError(_) => 40901, + Self::InvalidShieldedProofError(_) => 40902, + Self::InsufficientPoolNotesError(_) => 40903, + Self::InsufficientShieldedFeeError(_) => 40904, } } } diff --git a/packages/rs-dpp/src/errors/consensus/state/mod.rs b/packages/rs-dpp/src/errors/consensus/state/mod.rs index b0e64d6bc80..57585a79711 100644 --- a/packages/rs-dpp/src/errors/consensus/state/mod.rs +++ b/packages/rs-dpp/src/errors/consensus/state/mod.rs @@ -5,6 +5,7 @@ pub mod document; pub mod group; pub mod identity; pub mod prefunded_specialized_balances; +pub mod shielded; pub mod state_error; pub mod token; pub mod voting; diff --git a/packages/rs-dpp/src/errors/consensus/state/shielded/insufficient_pool_notes_error.rs b/packages/rs-dpp/src/errors/consensus/state/shielded/insufficient_pool_notes_error.rs new file mode 100644 index 00000000000..3358017ab95 --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/state/shielded/insufficient_pool_notes_error.rs @@ -0,0 +1,46 @@ +use crate::consensus::state::state_error::StateError; +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, +)] +#[platform_serialize(unversioned)] +#[error( + "Shielded pool has insufficient notes for outgoing transition: pool has {current_count} notes but minimum {minimum_required} required" +)] +pub struct InsufficientPoolNotesError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ + current_count: u64, + minimum_required: u64, +} + +impl InsufficientPoolNotesError { + pub fn new(current_count: u64, minimum_required: u64) -> Self { + Self { + current_count, + minimum_required, + } + } + + pub fn current_count(&self) -> u64 { + self.current_count + } + + pub fn minimum_required(&self) -> u64 { + self.minimum_required + } +} + +impl From for ConsensusError { + fn from(err: InsufficientPoolNotesError) -> Self { + Self::StateError(StateError::InsufficientPoolNotesError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/state/shielded/insufficient_shielded_fee_error.rs b/packages/rs-dpp/src/errors/consensus/state/shielded/insufficient_shielded_fee_error.rs new file mode 100644 index 00000000000..4bfcd288404 --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/state/shielded/insufficient_shielded_fee_error.rs @@ -0,0 +1,36 @@ +use crate::consensus::state::state_error::StateError; +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, +)] +#[platform_serialize(unversioned)] +#[error("Insufficient shielded transaction fee: {message}")] +pub struct InsufficientShieldedFeeError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ + message: String, +} + +impl InsufficientShieldedFeeError { + pub fn new(message: String) -> Self { + Self { message } + } + + pub fn message(&self) -> &str { + &self.message + } +} + +impl From for ConsensusError { + fn from(err: InsufficientShieldedFeeError) -> Self { + Self::StateError(StateError::InsufficientShieldedFeeError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/state/shielded/invalid_anchor_error.rs b/packages/rs-dpp/src/errors/consensus/state/shielded/invalid_anchor_error.rs new file mode 100644 index 00000000000..f0839902d57 --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/state/shielded/invalid_anchor_error.rs @@ -0,0 +1,39 @@ +use crate::consensus::state::state_error::StateError; +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, +)] +#[platform_serialize(unversioned)] +#[error( + "Anchor not found in the recorded anchors tree: {}", + hex::encode(anchor) +)] +pub struct InvalidAnchorError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ + anchor: [u8; 32], +} + +impl InvalidAnchorError { + pub fn new(anchor: [u8; 32]) -> Self { + Self { anchor } + } + + pub fn anchor(&self) -> [u8; 32] { + self.anchor + } +} + +impl From for ConsensusError { + fn from(err: InvalidAnchorError) -> Self { + Self::StateError(StateError::InvalidAnchorError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/state/shielded/invalid_shielded_proof_error.rs b/packages/rs-dpp/src/errors/consensus/state/shielded/invalid_shielded_proof_error.rs new file mode 100644 index 00000000000..b18b8c30f58 --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/state/shielded/invalid_shielded_proof_error.rs @@ -0,0 +1,36 @@ +use crate::consensus::state::state_error::StateError; +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, +)] +#[platform_serialize(unversioned)] +#[error("Invalid shielded transaction proof: {message}")] +pub struct InvalidShieldedProofError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ + message: String, +} + +impl InvalidShieldedProofError { + pub fn new(message: String) -> Self { + Self { message } + } + + pub fn message(&self) -> &str { + &self.message + } +} + +impl From for ConsensusError { + fn from(err: InvalidShieldedProofError) -> Self { + Self::StateError(StateError::InvalidShieldedProofError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/state/shielded/mod.rs b/packages/rs-dpp/src/errors/consensus/state/shielded/mod.rs new file mode 100644 index 00000000000..816eb366ac1 --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/state/shielded/mod.rs @@ -0,0 +1,11 @@ +pub mod insufficient_pool_notes_error; +pub mod insufficient_shielded_fee_error; +pub mod invalid_anchor_error; +pub mod invalid_shielded_proof_error; +pub mod nullifier_already_spent_error; + +pub use insufficient_pool_notes_error::*; +pub use insufficient_shielded_fee_error::*; +pub use invalid_anchor_error::*; +pub use invalid_shielded_proof_error::*; +pub use nullifier_already_spent_error::*; diff --git a/packages/rs-dpp/src/errors/consensus/state/shielded/nullifier_already_spent_error.rs b/packages/rs-dpp/src/errors/consensus/state/shielded/nullifier_already_spent_error.rs new file mode 100644 index 00000000000..a82430c6d0f --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/state/shielded/nullifier_already_spent_error.rs @@ -0,0 +1,36 @@ +use crate::consensus::state::state_error::StateError; +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, +)] +#[platform_serialize(unversioned)] +#[error("Nullifier has already been spent: {}", hex::encode(nullifier))] +pub struct NullifierAlreadySpentError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ + nullifier: [u8; 32], +} + +impl NullifierAlreadySpentError { + pub fn new(nullifier: [u8; 32]) -> Self { + Self { nullifier } + } + + pub fn nullifier(&self) -> [u8; 32] { + self.nullifier + } +} + +impl From for ConsensusError { + fn from(err: NullifierAlreadySpentError) -> Self { + Self::StateError(StateError::NullifierAlreadySpentError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/state/state_error.rs b/packages/rs-dpp/src/errors/consensus/state/state_error.rs index f3d3e9ad731..672c709198a 100644 --- a/packages/rs-dpp/src/errors/consensus/state/state_error.rs +++ b/packages/rs-dpp/src/errors/consensus/state/state_error.rs @@ -4,6 +4,11 @@ use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; use thiserror::Error; use crate::consensus::state::address_funds::{AddressDoesNotExistError, AddressInvalidNonceError, AddressNotEnoughFundsError, AddressesNotEnoughFundsError}; +use crate::consensus::state::shielded::insufficient_pool_notes_error::InsufficientPoolNotesError; +use crate::consensus::state::shielded::insufficient_shielded_fee_error::InsufficientShieldedFeeError; +use crate::consensus::state::shielded::invalid_anchor_error::InvalidAnchorError; +use crate::consensus::state::shielded::invalid_shielded_proof_error::InvalidShieldedProofError; +use crate::consensus::state::shielded::nullifier_already_spent_error::NullifierAlreadySpentError; use crate::consensus::state::data_contract::data_contract_already_present_error::DataContractAlreadyPresentError; use crate::consensus::state::data_contract::data_contract_config_update_error::DataContractConfigUpdateError; use crate::consensus::state::data_contract::data_contract_is_readonly_error::DataContractIsReadonlyError; @@ -334,6 +339,21 @@ pub enum StateError { #[error(transparent)] AddressInvalidNonceError(AddressInvalidNonceError), + + #[error(transparent)] + InvalidAnchorError(InvalidAnchorError), + + #[error(transparent)] + NullifierAlreadySpentError(NullifierAlreadySpentError), + + #[error(transparent)] + InvalidShieldedProofError(InvalidShieldedProofError), + + #[error(transparent)] + InsufficientPoolNotesError(InsufficientPoolNotesError), + + #[error(transparent)] + InsufficientShieldedFeeError(InsufficientShieldedFeeError), } impl From for ConsensusError { diff --git a/packages/rs-dpp/src/lib.rs b/packages/rs-dpp/src/lib.rs index d160ad35b99..e73fb05c251 100644 --- a/packages/rs-dpp/src/lib.rs +++ b/packages/rs-dpp/src/lib.rs @@ -73,6 +73,7 @@ pub mod core_types; pub mod address_funds; pub mod group; +pub mod shielded; pub mod withdrawal; pub use async_trait; diff --git a/packages/rs-dpp/src/shielded/builder.rs b/packages/rs-dpp/src/shielded/builder.rs new file mode 100644 index 00000000000..878ee41c015 --- /dev/null +++ b/packages/rs-dpp/src/shielded/builder.rs @@ -0,0 +1,611 @@ +//! Convenience builders for constructing shielded state transitions. +//! +//! These functions encapsulate the full Orchard bundle construction pipeline: +//! builder configuration, proof generation, signature application, +//! and serialization into platform state transitions. +//! +//! Requires the `shielded-bundle-building` feature, which pulls in +//! `grovedb-commitment-tree` (and transitively the `orchard` crate). +//! +//! # Example +//! +//! ```ignore +//! use dpp::shielded::builder::*; +//! use grovedb_commitment_tree::{SpendingKey, FullViewingKey, Scope, ProvingKey}; +//! +//! // Derive recipient address +//! let sk = SpendingKey::from_bytes(seed)?; +//! let fvk = FullViewingKey::from(&sk); +//! let recipient = OrchardAddress::from_raw_bytes( +//! &fvk.address_at(0, Scope::External).to_raw_address_bytes(), +//! ); +//! +//! // Build a shield transition +//! let pk = ProvingKey::build(); +//! let st = build_shield_transition( +//! &recipient, shield_amount, inputs, fee_strategy, +//! &signer, 0, &pk, [0u8; 36], platform_version, +//! )?; +//! ``` + +use std::collections::BTreeMap; + +use grovedb_commitment_tree::{ + Anchor, Authorized, Builder, Bundle, BundleType, DashMemo, Flags as OrchardFlags, + FullViewingKey, MerklePath, Note, NoteValue, PaymentAddress, ProvingKey, SpendAuthorizingKey, +}; +use rand::rngs::OsRng; + +use crate::address_funds::AddressFundsFeeStrategy; +use crate::address_funds::{OrchardAddress, PlatformAddress}; +use crate::fee::Credits; +use crate::identity::core_script::CoreScript; +use crate::identity::signer::Signer; +use crate::prelude::{AddressNonce, UserFeeIncrease}; +use crate::shielded::{compute_minimum_shielded_fee, compute_platform_sighash, SerializedAction}; +use crate::state_transition::shield_from_asset_lock_transition::methods::ShieldFromAssetLockTransitionMethodsV0; +use crate::state_transition::shield_from_asset_lock_transition::ShieldFromAssetLockTransition; +use crate::state_transition::shield_transition::methods::ShieldTransitionMethodsV0; +use crate::state_transition::shield_transition::ShieldTransition; +use crate::state_transition::shielded_transfer_transition::methods::ShieldedTransferTransitionMethodsV0; +use crate::state_transition::shielded_transfer_transition::ShieldedTransferTransition; +use crate::state_transition::shielded_withdrawal_transition::methods::ShieldedWithdrawalTransitionMethodsV0; +use crate::state_transition::shielded_withdrawal_transition::ShieldedWithdrawalTransition; +use crate::state_transition::unshield_transition::methods::UnshieldTransitionMethodsV0; +use crate::state_transition::unshield_transition::UnshieldTransition; +use crate::state_transition::StateTransition; +use crate::withdrawal::Pooling; +use crate::ProtocolError; +use platform_version::version::PlatformVersion; + +/// A note that can be spent in a shielded transaction, paired with its +/// Merkle inclusion path in the commitment tree. +pub struct SpendableNote { + /// The Orchard note to spend. + pub note: Note, + /// Merkle path proving the note's commitment exists in the tree. + pub merkle_path: MerklePath, +} + +/// Converts an [`OrchardAddress`] to an Orchard [`PaymentAddress`]. +/// +/// Returns an error if `pk_d` is not a valid Pallas curve point. +pub fn orchard_address_to_payment_address( + address: &OrchardAddress, +) -> Result { + let raw = address.to_raw_bytes(); + Option::from(PaymentAddress::from_raw_address_bytes(&raw)).ok_or_else(|| { + ProtocolError::DecodingError( + "OrchardAddress pk_d is not a valid Pallas curve point".to_string(), + ) + }) +} + +/// Serializes an authorized Orchard bundle into the raw fields used by +/// state transition constructors. +/// +/// Returns `(actions, flags, value_balance, anchor, proof, binding_signature)`. +pub fn serialize_authorized_bundle( + bundle: &Bundle, +) -> (Vec, u8, i64, [u8; 32], Vec, [u8; 64]) { + let actions: Vec = bundle + .actions() + .iter() + .map(|action| { + let enc = action.encrypted_note(); + let mut encrypted_note = Vec::with_capacity(216); + encrypted_note.extend_from_slice(&enc.epk_bytes); + encrypted_note.extend_from_slice(enc.enc_ciphertext.as_ref()); + encrypted_note.extend_from_slice(&enc.out_ciphertext); + SerializedAction { + nullifier: action.nullifier().to_bytes(), + rk: <[u8; 32]>::from(action.rk()), + cmx: action.cmx().to_bytes(), + encrypted_note, + cv_net: action.cv_net().to_bytes(), + spend_auth_sig: <[u8; 64]>::from(action.authorization()), + } + }) + .collect(); + let flags = bundle.flags().to_byte(); + let value_balance = *bundle.value_balance(); + let anchor = bundle.anchor().to_bytes(); + let proof = bundle.authorization().proof().as_ref().to_vec(); + let binding_sig = <[u8; 64]>::from(bundle.authorization().binding_signature()); + (actions, flags, value_balance, anchor, proof, binding_sig) +} + +// --------------------------------------------------------------------------- +// Internal helpers +// --------------------------------------------------------------------------- + +/// Builds an output-only Orchard bundle (no spends). +/// +/// Used by Shield and ShieldFromAssetLock transitions where funds enter +/// the shielded pool from transparent sources. +fn build_output_only_bundle( + recipient: &OrchardAddress, + amount: u64, + memo: [u8; 36], + proving_key: &ProvingKey, +) -> Result, ProtocolError> { + let payment_address = orchard_address_to_payment_address(recipient)?; + let anchor = Anchor::empty_tree(); + let mut builder = Builder::::new( + BundleType::Transactional { + flags: OrchardFlags::SPENDS_DISABLED, + bundle_required: false, + }, + anchor, + ); + + builder + .add_output(None, payment_address, NoteValue::from_raw(amount), memo) + .map_err(|e| ProtocolError::Generic(format!("failed to add output: {:?}", e)))?; + + prove_and_sign_bundle(builder, proving_key, &[], &[]) +} + +/// Builds a spend+output Orchard bundle. +/// +/// Used by ShieldedTransfer, Unshield, and ShieldedWithdrawal where funds +/// are spent from existing notes. +fn build_spend_bundle( + spends: Vec, + recipient: &OrchardAddress, + output_amount: u64, + memo: [u8; 36], + fvk: &FullViewingKey, + ask: &SpendAuthorizingKey, + anchor: Anchor, + proving_key: &ProvingKey, + extra_sighash_data: &[u8], +) -> Result, ProtocolError> { + let payment_address = orchard_address_to_payment_address(recipient)?; + + let mut builder = Builder::::new(BundleType::DEFAULT, anchor); + + for spend in spends { + builder + .add_spend(fvk.clone(), spend.note, spend.merkle_path) + .map_err(|e| ProtocolError::Generic(format!("failed to add spend: {:?}", e)))?; + } + + builder + .add_output( + None, + payment_address, + NoteValue::from_raw(output_amount), + memo, + ) + .map_err(|e| ProtocolError::Generic(format!("failed to add output: {:?}", e)))?; + + prove_and_sign_bundle(builder, proving_key, &[ask.clone()], extra_sighash_data) +} + +/// Takes a configured Builder, generates the proof, computes the platform +/// sighash, and applies signatures. +fn prove_and_sign_bundle( + builder: Builder, + proving_key: &ProvingKey, + signing_keys: &[SpendAuthorizingKey], + extra_sighash_data: &[u8], +) -> Result, ProtocolError> { + let mut rng = OsRng; + + let (unauthorized, _) = builder + .build::(&mut rng) + .map_err(|e| ProtocolError::Generic(format!("failed to build bundle: {:?}", e)))? + .ok_or_else(|| ProtocolError::Generic("bundle was empty after build".to_string()))?; + + let bundle_commitment: [u8; 32] = unauthorized.commitment().into(); + let sighash = compute_platform_sighash(&bundle_commitment, extra_sighash_data); + + let proven = unauthorized + .create_proof(proving_key, &mut rng) + .map_err(|e| ProtocolError::Generic(format!("failed to create proof: {:?}", e)))?; + + proven + .apply_signatures(rng, sighash, signing_keys) + .map_err(|e| ProtocolError::Generic(format!("failed to apply signatures: {:?}", e))) +} + +// --------------------------------------------------------------------------- +// Public builder functions +// --------------------------------------------------------------------------- + +/// Builds a Shield state transition (transparent platform addresses -> shielded pool). +/// +/// Constructs an output-only Orchard bundle (no spends), proves it, signs the +/// transparent input witnesses, and returns a ready-to-broadcast `StateTransition`. +/// +/// # Parameters +/// - `recipient` - Orchard address to receive the shielded note +/// - `shield_amount` - Amount of credits to shield +/// - `inputs` - Platform address inputs with their nonces and balances +/// - `fee_strategy` - How to deduct fees from the transparent inputs +/// - `signer` - Signs each input address witness (ECDSA) +/// - `user_fee_increase` - Fee multiplier (0 = 100% base fee) +/// - `proving_key` - Halo 2 proving key (cache with `OnceLock` — ~30s to build) +/// - `memo` - 36-byte structured memo for the recipient (4-byte type tag + 32-byte payload) +/// - `platform_version` - Protocol version +pub fn build_shield_transition>( + recipient: &OrchardAddress, + shield_amount: u64, + inputs: BTreeMap, + fee_strategy: AddressFundsFeeStrategy, + signer: &S, + user_fee_increase: UserFeeIncrease, + proving_key: &ProvingKey, + memo: [u8; 36], + platform_version: &PlatformVersion, +) -> Result { + if fee_strategy.is_empty() { + return Err(ProtocolError::Generic( + "fee_strategy must have at least one step".to_string(), + )); + } + + let bundle = build_output_only_bundle(recipient, shield_amount, memo, proving_key)?; + let (actions, flags, value_balance, anchor, proof, binding_sig) = + serialize_authorized_bundle(&bundle); + + ShieldTransition::try_from_bundle_with_signer( + inputs, + actions, + flags, + value_balance, + anchor, + proof, + binding_sig, + fee_strategy, + signer, + user_fee_increase, + platform_version, + ) +} + +/// Builds a ShieldFromAssetLock state transition (core asset lock -> shielded pool). +/// +/// Like Shield, constructs an output-only Orchard bundle. The funds come from +/// a core asset lock proof rather than platform address inputs. +/// +/// # Parameters +/// - `recipient` - Orchard address to receive the shielded note +/// - `shield_amount` - Amount of credits to shield (from the asset lock) +/// - `asset_lock_proof` - Proof that funds are locked on core chain +/// - `asset_lock_private_key` - Private key for the asset lock (signs the transition) +/// - `user_fee_increase` - Fee multiplier (0 = 100% base fee) +/// - `proving_key` - Halo 2 proving key +/// - `memo` - 36-byte structured memo for the recipient (4-byte type tag + 32-byte payload) +/// - `platform_version` - Protocol version +pub fn build_shield_from_asset_lock_transition( + recipient: &OrchardAddress, + shield_amount: u64, + asset_lock_proof: crate::prelude::AssetLockProof, + asset_lock_private_key: &[u8], + user_fee_increase: UserFeeIncrease, + proving_key: &ProvingKey, + memo: [u8; 36], + platform_version: &PlatformVersion, +) -> Result { + let bundle = build_output_only_bundle(recipient, shield_amount, memo, proving_key)?; + let (actions, flags, value_balance, anchor, proof, binding_sig) = + serialize_authorized_bundle(&bundle); + + ShieldFromAssetLockTransition::try_from_asset_lock_with_bundle( + asset_lock_proof, + asset_lock_private_key, + actions, + flags, + value_balance, + anchor, + proof, + binding_sig, + user_fee_increase, + platform_version, + ) +} + +/// Builds a ShieldedTransfer state transition (shielded pool -> shielded pool). +/// +/// Spends existing notes and creates a new note for the recipient. The shielded +/// fee is deducted from the spent notes. Any remaining change is returned to +/// the `change_address`. +/// +/// # Parameters +/// - `spends` - Notes to spend with their Merkle paths +/// - `recipient` - Orchard address to receive the transferred note +/// - `transfer_amount` - Amount to transfer to the recipient +/// - `change_address` - Orchard address for change output (if any) +/// - `fvk` - Full viewing key for spend authorization +/// - `ask` - Spend authorizing key for RedPallas signatures +/// - `anchor` - Merkle root of the commitment tree +/// - `proving_key` - Halo 2 proving key +/// - `memo` - 36-byte structured memo for the recipient (4-byte type tag + 32-byte payload) +/// - `fee` - Optional fee override; if `None`, the minimum fee is computed automatically. +/// If `Some`, must be >= the minimum fee. +/// - `platform_version` - Protocol version +pub fn build_shielded_transfer_transition( + spends: Vec, + recipient: &OrchardAddress, + transfer_amount: u64, + change_address: &OrchardAddress, + fvk: &FullViewingKey, + ask: &SpendAuthorizingKey, + anchor: Anchor, + proving_key: &ProvingKey, + memo: [u8; 36], + fee: Option, + platform_version: &PlatformVersion, +) -> Result { + let total_spent: u64 = spends.iter().map(|s| s.note.value().inner()).sum(); + + // Conservative action count: at least (spends, 2) since we always have + // a recipient output and likely a change output. + let num_actions = spends.len().max(2); + let min_fee = compute_minimum_shielded_fee(num_actions, platform_version); + let effective_fee = match fee { + Some(f) if f < min_fee => { + return Err(ProtocolError::Generic(format!( + "fee {} is below minimum required fee {}", + f, min_fee + ))); + } + Some(f) => f, + None => min_fee, + }; + + let required = transfer_amount + .checked_add(effective_fee) + .ok_or_else(|| ProtocolError::Generic("fee + transfer_amount overflows u64".to_string()))?; + if required > total_spent { + return Err(ProtocolError::Generic(format!( + "transfer amount {} + fee {} = {} exceeds total spendable value {}", + transfer_amount, effective_fee, required, total_spent + ))); + } + + let change_amount = total_spent - required; + + let recipient_payment = orchard_address_to_payment_address(recipient)?; + + let mut builder = Builder::::new(BundleType::DEFAULT, anchor); + + for spend in spends { + builder + .add_spend(fvk.clone(), spend.note, spend.merkle_path) + .map_err(|e| ProtocolError::Generic(format!("failed to add spend: {:?}", e)))?; + } + + // Primary output to recipient + builder + .add_output( + None, + recipient_payment, + NoteValue::from_raw(transfer_amount), + memo, + ) + .map_err(|e| ProtocolError::Generic(format!("failed to add output: {:?}", e)))?; + + // Change output (if any) + if change_amount > 0 { + let change_payment = orchard_address_to_payment_address(change_address)?; + builder + .add_output( + None, + change_payment, + NoteValue::from_raw(change_amount), + [0u8; 36], + ) + .map_err(|e| ProtocolError::Generic(format!("failed to add change output: {:?}", e)))?; + } + + // ShieldedTransfer has no extra_data in sighash + let bundle = prove_and_sign_bundle(builder, proving_key, &[ask.clone()], &[])?; + let (actions, flags, value_balance, anchor_bytes, proof, binding_sig) = + serialize_authorized_bundle(&bundle); + + // value_balance = effective_fee (the amount leaving the shielded pool as fee) + ShieldedTransferTransition::try_from_bundle( + actions, + flags, + value_balance as u64, + anchor_bytes, + proof, + binding_sig, + platform_version, + ) +} + +/// Builds an Unshield state transition (shielded pool -> platform address). +/// +/// Spends existing notes and sends part of the value to a transparent platform +/// address. The shielded fee is deducted from the spent notes. Any remaining +/// value is returned to the shielded `change_address`. +/// +/// # Parameters +/// - `spends` - Notes to spend with their Merkle paths +/// - `output_address` - Platform address to receive the unshielded funds +/// - `unshield_amount` - Amount to unshield to the platform address +/// - `change_address` - Orchard address for change output +/// - `fvk` - Full viewing key for spend authorization +/// - `ask` - Spend authorizing key for RedPallas signatures +/// - `anchor` - Merkle root of the commitment tree +/// - `proving_key` - Halo 2 proving key +/// - `memo` - 36-byte structured memo for the change output (4-byte type tag + 32-byte payload) +/// - `fee` - Optional fee override; if `None`, the minimum fee is computed automatically. +/// If `Some`, must be >= the minimum fee. +/// - `platform_version` - Protocol version +pub fn build_unshield_transition( + spends: Vec, + output_address: PlatformAddress, + unshield_amount: u64, + change_address: &OrchardAddress, + fvk: &FullViewingKey, + ask: &SpendAuthorizingKey, + anchor: Anchor, + proving_key: &ProvingKey, + memo: [u8; 36], + fee: Option, + platform_version: &PlatformVersion, +) -> Result { + let total_spent: u64 = spends.iter().map(|s| s.note.value().inner()).sum(); + + // Conservative action count: at least (spends, 1) since we have a change output. + let num_actions = spends.len().max(1); + let min_fee = compute_minimum_shielded_fee(num_actions, platform_version); + let effective_fee = match fee { + Some(f) if f < min_fee => { + return Err(ProtocolError::Generic(format!( + "fee {} is below minimum required fee {}", + f, min_fee + ))); + } + Some(f) => f, + None => min_fee, + }; + + let required = unshield_amount + .checked_add(effective_fee) + .ok_or_else(|| ProtocolError::Generic("fee + unshield_amount overflows u64".to_string()))?; + if required > total_spent { + return Err(ProtocolError::Generic(format!( + "unshield amount {} + fee {} = {} exceeds total spendable value {}", + unshield_amount, effective_fee, required, total_spent + ))); + } + + let change_amount = total_spent - required; + + // Unshield extra_data = output_address.to_bytes() || amount.to_le_bytes() + let mut extra_sighash_data = output_address.to_bytes(); + extra_sighash_data.extend_from_slice(&unshield_amount.to_le_bytes()); + + let bundle = build_spend_bundle( + spends, + change_address, + change_amount, + memo, + fvk, + ask, + anchor, + proving_key, + &extra_sighash_data, + )?; + + let (actions, flags, value_balance, anchor_bytes, proof, binding_sig) = + serialize_authorized_bundle(&bundle); + + UnshieldTransition::try_from_bundle( + output_address, + unshield_amount, + actions, + flags, + value_balance, + anchor_bytes, + proof, + binding_sig, + platform_version, + ) +} + +/// Builds a ShieldedWithdrawal state transition (shielded pool -> core L1 address). +/// +/// Spends existing notes and withdraws value to a core chain script output. +/// The shielded fee is deducted from the spent notes. Any remaining value is +/// returned to the shielded `change_address`. +/// +/// # Parameters +/// - `spends` - Notes to spend with their Merkle paths +/// - `withdrawal_amount` - Amount to withdraw to the core chain +/// - `output_script` - Core chain script to receive the funds +/// - `core_fee_per_byte` - Core chain fee rate +/// - `pooling` - Withdrawal pooling strategy +/// - `change_address` - Orchard address for change output +/// - `fvk` - Full viewing key for spend authorization +/// - `ask` - Spend authorizing key for RedPallas signatures +/// - `anchor` - Merkle root of the commitment tree +/// - `proving_key` - Halo 2 proving key +/// - `memo` - 36-byte structured memo for the change output (4-byte type tag + 32-byte payload) +/// - `fee` - Optional fee override; if `None`, the minimum fee is computed automatically. +/// If `Some`, must be >= the minimum fee. +/// - `platform_version` - Protocol version +pub fn build_shielded_withdrawal_transition( + spends: Vec, + withdrawal_amount: u64, + output_script: CoreScript, + core_fee_per_byte: u32, + pooling: Pooling, + change_address: &OrchardAddress, + fvk: &FullViewingKey, + ask: &SpendAuthorizingKey, + anchor: Anchor, + proving_key: &ProvingKey, + memo: [u8; 36], + fee: Option, + platform_version: &PlatformVersion, +) -> Result { + let total_spent: u64 = spends.iter().map(|s| s.note.value().inner()).sum(); + + // Conservative action count: at least (spends, 1) since we have a change output. + let num_actions = spends.len().max(1); + let min_fee = compute_minimum_shielded_fee(num_actions, platform_version); + let effective_fee = match fee { + Some(f) if f < min_fee => { + return Err(ProtocolError::Generic(format!( + "fee {} is below minimum required fee {}", + f, min_fee + ))); + } + Some(f) => f, + None => min_fee, + }; + + let required = withdrawal_amount + .checked_add(effective_fee) + .ok_or_else(|| { + ProtocolError::Generic("fee + withdrawal_amount overflows u64".to_string()) + })?; + if required > total_spent { + return Err(ProtocolError::Generic(format!( + "withdrawal amount {} + fee {} = {} exceeds total spendable value {}", + withdrawal_amount, effective_fee, required, total_spent + ))); + } + + let change_amount = total_spent - required; + + // ShieldedWithdrawal extra_data = output_script.as_bytes() || amount.to_le_bytes() + let mut extra_sighash_data = output_script.as_bytes().to_vec(); + extra_sighash_data.extend_from_slice(&withdrawal_amount.to_le_bytes()); + + let bundle = build_spend_bundle( + spends, + change_address, + change_amount, + memo, + fvk, + ask, + anchor, + proving_key, + &extra_sighash_data, + )?; + + let (actions, flags, value_balance, anchor_bytes, proof, binding_sig) = + serialize_authorized_bundle(&bundle); + + ShieldedWithdrawalTransition::try_from_bundle( + withdrawal_amount, + actions, + flags, + value_balance, + anchor_bytes, + proof, + binding_sig, + core_fee_per_byte, + pooling, + output_script, + platform_version, + ) +} diff --git a/packages/rs-dpp/src/shielded/mod.rs b/packages/rs-dpp/src/shielded/mod.rs new file mode 100644 index 00000000000..867e7da0d44 --- /dev/null +++ b/packages/rs-dpp/src/shielded/mod.rs @@ -0,0 +1,170 @@ +#[cfg(feature = "shielded-bundle-building")] +pub mod builder; + +use bincode::{Decode, Encode}; +#[cfg(feature = "state-transition-serde-conversion")] +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; + +use crate::fee::Credits; +use platform_version::version::PlatformVersion; + +/// Permanent storage bytes per shielded action: +/// 280 bytes in BulkAppendTree (32 cmx + 32 rho + 216 encrypted note) +/// + 32 bytes in nullifier tree = 312 bytes total. +pub const SHIELDED_STORAGE_BYTES_PER_ACTION: u64 = 312; + +/// Domain separator for Platform sighash computation. +const SIGHASH_DOMAIN: &[u8] = b"DashPlatformSighash"; + +/// Computes the platform sighash from an Orchard bundle commitment and optional +/// transparent field data. +/// +/// The sighash is computed as: +/// `SHA-256(SIGHASH_DOMAIN || bundle_commitment || extra_data)` +/// +/// This binds transparent state transition fields (like `output_address` and `amount` +/// in unshield transitions) to the Orchard signatures, preventing replay attacks +/// where an attacker substitutes transparent fields while reusing a valid Orchard bundle. +/// +/// The same computation must be used on both the signing (client) and verification +/// (platform) sides. For transitions without transparent fields (shield and +/// shielded_transfer), `extra_data` is empty. +pub fn compute_platform_sighash(bundle_commitment: &[u8; 32], extra_data: &[u8]) -> [u8; 32] { + let mut hasher = Sha256::new(); + hasher.update(SIGHASH_DOMAIN); + hasher.update(bundle_commitment); + hasher.update(extra_data); + hasher.finalize().into() +} + +/// Computes the minimum fee (in credits) for a shielded state transition. +/// +/// The fee formula mirrors the on-chain validation in `validate_minimum_shielded_fee`: +/// `min_fee = proof_verification_fee + num_actions × (processing_fee + storage_fee)` +/// +/// where `storage_fee = SHIELDED_STORAGE_BYTES_PER_ACTION × (disk + processing) credits/byte`. +/// +/// # Parameters +/// - `num_actions` — number of Orchard actions in the bundle +/// - `platform_version` — protocol version (determines fee constants) +pub fn compute_minimum_shielded_fee( + num_actions: usize, + platform_version: &PlatformVersion, +) -> Credits { + let constants = &platform_version + .drive_abci + .validation_and_processing + .event_constants; + let storage = &platform_version.fee_version.storage; + let storage_fee = SHIELDED_STORAGE_BYTES_PER_ACTION + * (storage.storage_disk_usage_credit_per_byte + storage.storage_processing_credit_per_byte); + let per_action = constants.shielded_per_action_processing_fee + storage_fee; + constants.shielded_proof_verification_fee + num_actions as u64 * per_action +} + +/// Serde helper for `[u8; 64]` fields (serde only supports arrays up to 32). +#[cfg(feature = "state-transition-serde-conversion")] +pub(crate) mod serde_bytes_64 { + use serde::{Deserialize, Deserializer, Serializer}; + + pub fn serialize(bytes: &[u8; 64], serializer: S) -> Result { + serializer.serialize_bytes(bytes) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<[u8; 64], D::Error> { + let vec = >::deserialize(deserializer)?; + vec.try_into().map_err(|v: Vec| { + serde::de::Error::custom(format!("expected 64 bytes, got {}", v.len())) + }) + } +} + +/// Common Orchard bundle parameters shared across all shielded transition types. +/// +/// Groups the fields that every shielded transition carries identically: +/// the serialized actions, bundle flags, commitment tree anchor, Halo 2 proof, +/// and RedPallas binding signature. Using this struct reduces parameter counts +/// in SDK helper functions from 10-12 down to 5-8. +pub struct OrchardBundleParams { + /// The serialized Orchard actions (spends + outputs). + pub actions: Vec, + /// Bundle flags byte. + pub flags: u8, + /// Merkle root of the commitment tree at bundle creation time (32 bytes). + pub anchor: [u8; 32], + /// Halo 2 zero-knowledge proof bytes. + pub proof: Vec, + /// RedPallas binding signature (64 bytes) over the bundle's value balance. + pub binding_signature: [u8; 64], +} + +/// A serialized Orchard action extracted from a bundle. +/// +/// Each Orchard action structurally contains one spend and one output. The spend +/// consumes a previously created note (revealing its nullifier), while the output +/// creates a new note (publishing its commitment). Although paired in the same struct, +/// observers cannot link which prior note was spent or what value the new note holds — +/// the zero-knowledge proof ensures privacy. +/// +/// These fields are raw bytes suitable for network serialization. During validation, +/// they are parsed back into typed Orchard structs and verified via `BatchValidator` +/// (Halo 2 proof + RedPallas signatures). +/// +/// All fields except `spend_auth_sig` are covered by the Orchard bundle commitment +/// (BLAKE2b-256 per ZIP-244), which feeds into the platform sighash. The signatures +/// and proof are verified separately and are not part of the commitment. +#[derive(Debug, Clone, Encode, Decode, PartialEq)] +#[cfg_attr( + feature = "state-transition-serde-conversion", + derive(Serialize, Deserialize), + serde(rename_all = "camelCase") +)] +pub struct SerializedAction { + /// Unique tag derived from the spent note's position and spending key. + /// Published on-chain to prevent double-spends: if this nullifier already + /// exists in the nullifier set, the transaction is rejected. The nullifier + /// is deterministic for a given note but unlinkable to the note's commitment, + /// preserving sender privacy. + pub nullifier: [u8; 32], + + /// Randomized spend validating key (RedPallas verification key). + /// Derived from the spender's full viewing key with per-action randomness. + /// Used to verify `spend_auth_sig`, proving the spender controls the spending + /// key for the consumed note without revealing which key it is. + pub rk: [u8; 32], + + /// Extracted note commitment for the newly created output note. + /// This is added to the commitment tree after the transition is applied, + /// allowing the recipient to later spend it. The commitment hides the note's + /// value, recipient, and randomness — only the recipient (who knows the + /// decryption key) can identify and spend this note. + pub cmx: [u8; 32], + + /// Encrypted note ciphertext (216 bytes = epk 32 + enc_ciphertext 104 + out_ciphertext 80). + /// Contains the `TransmittedNoteCiphertext` fields packed contiguously: + /// - `epk`: ephemeral public key for Diffie-Hellman key agreement (32 bytes) + /// - `enc_ciphertext`: note plaintext encrypted to the recipient (104 bytes = 52 compact + 36 memo + 16 AEAD tag) + /// - `out_ciphertext`: encrypted to the sender for wallet recovery (80 bytes) + /// Stored on-chain so recipients can scan and decrypt notes addressed to them. + /// Only the intended recipient (or sender) can decrypt; all others see random bytes. + pub encrypted_note: Vec, + + /// Value commitment (Pedersen commitment to the note's value). + /// Commits to the value flowing through this action without revealing it. + /// The binding signature later proves that the sum of all `cv_net` commitments + /// across actions is consistent with the declared `value_balance`, ensuring + /// no credits are created or destroyed. + pub cv_net: [u8; 32], + + /// RedPallas spend authorization signature over the platform sighash. + /// Proves the spender authorized this specific bundle (including all actions, + /// value_balance, anchor, and any bound transparent fields). Verified against + /// `rk` during batch validation. This prevents replay attacks — a valid + /// signature from one transition cannot be reused in another. + #[cfg_attr( + feature = "state-transition-serde-conversion", + serde(with = "serde_bytes_64") + )] + pub spend_auth_sig: [u8; 64], +} diff --git a/packages/rs-dpp/src/state_transition/mod.rs b/packages/rs-dpp/src/state_transition/mod.rs index 8f41b601ded..852cc086b2a 100644 --- a/packages/rs-dpp/src/state_transition/mod.rs +++ b/packages/rs-dpp/src/state_transition/mod.rs @@ -136,8 +136,21 @@ use crate::state_transition::identity_update_transition::{ }; use crate::state_transition::masternode_vote_transition::MasternodeVoteTransition; use crate::state_transition::masternode_vote_transition::MasternodeVoteTransitionSignable; +use crate::state_transition::shield_from_asset_lock_transition::{ + ShieldFromAssetLockTransition, ShieldFromAssetLockTransitionSignable, +}; +use crate::state_transition::shield_transition::{ShieldTransition, ShieldTransitionSignable}; +use crate::state_transition::shielded_transfer_transition::{ + ShieldedTransferTransition, ShieldedTransferTransitionSignable, +}; +use crate::state_transition::shielded_withdrawal_transition::{ + ShieldedWithdrawalTransition, ShieldedWithdrawalTransitionSignable, +}; #[cfg(feature = "state-transition-signing")] use crate::state_transition::state_transitions::document::batch_transition::methods::v0::DocumentsBatchTransitionMethodsV0; +use crate::state_transition::unshield_transition::{ + UnshieldTransition, UnshieldTransitionSignable, +}; use state_transitions::document::batch_transition::batched_transition::token_transition::TokenTransition; pub use state_transitions::*; @@ -162,6 +175,11 @@ macro_rules! call_method { StateTransition::AddressFundsTransfer(st) => st.$method($args), StateTransition::AddressFundingFromAssetLock(st) => st.$method($args), StateTransition::AddressCreditWithdrawal(st) => st.$method($args), + StateTransition::Shield(st) => st.$method($args), + StateTransition::ShieldedTransfer(st) => st.$method($args), + StateTransition::Unshield(st) => st.$method($args), + StateTransition::ShieldFromAssetLock(st) => st.$method($args), + StateTransition::ShieldedWithdrawal(st) => st.$method($args), } }; ($state_transition:expr, $method:ident ) => { @@ -181,6 +199,11 @@ macro_rules! call_method { StateTransition::AddressFundsTransfer(st) => st.$method(), StateTransition::AddressFundingFromAssetLock(st) => st.$method(), StateTransition::AddressCreditWithdrawal(st) => st.$method(), + StateTransition::Shield(st) => st.$method(), + StateTransition::ShieldedTransfer(st) => st.$method(), + StateTransition::Unshield(st) => st.$method(), + StateTransition::ShieldFromAssetLock(st) => st.$method(), + StateTransition::ShieldedWithdrawal(st) => st.$method(), } }; } @@ -203,6 +226,11 @@ macro_rules! call_getter_method_identity_signed { StateTransition::AddressFundsTransfer(_) => None, StateTransition::AddressFundingFromAssetLock(_) => None, StateTransition::AddressCreditWithdrawal(_) => None, + StateTransition::Shield(_) => None, + StateTransition::ShieldedTransfer(_) => None, + StateTransition::Unshield(_) => None, + StateTransition::ShieldFromAssetLock(_) => None, + StateTransition::ShieldedWithdrawal(_) => None, } }; ($state_transition:expr, $method:ident ) => { @@ -222,6 +250,11 @@ macro_rules! call_getter_method_identity_signed { StateTransition::AddressFundsTransfer(_) => None, StateTransition::AddressFundingFromAssetLock(_) => None, StateTransition::AddressCreditWithdrawal(_) => None, + StateTransition::Shield(_) => None, + StateTransition::ShieldedTransfer(_) => None, + StateTransition::Unshield(_) => None, + StateTransition::ShieldFromAssetLock(_) => None, + StateTransition::ShieldedWithdrawal(_) => None, } }; } @@ -244,6 +277,11 @@ macro_rules! call_method_identity_signed { StateTransition::AddressFundsTransfer(_) => {} StateTransition::AddressFundingFromAssetLock(_) => {} StateTransition::AddressCreditWithdrawal(_) => {} + StateTransition::Shield(_) => {} + StateTransition::ShieldedTransfer(_) => {} + StateTransition::Unshield(_) => {} + StateTransition::ShieldFromAssetLock(_) => {} + StateTransition::ShieldedWithdrawal(_) => {} } }; ($state_transition:expr, $method:ident ) => { @@ -263,6 +301,11 @@ macro_rules! call_method_identity_signed { StateTransition::AddressFundsTransfer(_) => {} StateTransition::AddressFundingFromAssetLock(_) => {} StateTransition::AddressCreditWithdrawal(_) => {} + StateTransition::Shield(_) => {} + StateTransition::ShieldedTransfer(_) => {} + StateTransition::Unshield(_) => {} + StateTransition::ShieldFromAssetLock(_) => {} + StateTransition::ShieldedWithdrawal(_) => {} } }; } @@ -300,6 +343,21 @@ macro_rules! call_errorable_method_identity_signed { StateTransition::AddressCreditWithdrawal(_) => Err(ProtocolError::CorruptedCodeExecution( "address credit withdrawal can not be called for identity signing".to_string(), )), + StateTransition::Shield(_) => Err(ProtocolError::CorruptedCodeExecution( + "shield transition can not be called for identity signing".to_string(), + )), + StateTransition::ShieldedTransfer(_) => Err(ProtocolError::CorruptedCodeExecution( + "shielded transfer transition can not be called for identity signing".to_string(), + )), + StateTransition::Unshield(_) => Err(ProtocolError::CorruptedCodeExecution( + "unshield transition can not be called for identity signing".to_string(), + )), + StateTransition::ShieldFromAssetLock(_) => Err(ProtocolError::CorruptedCodeExecution( + "shield from asset lock transition can not be called for identity signing".to_string(), + )), + StateTransition::ShieldedWithdrawal(_) => Err(ProtocolError::CorruptedCodeExecution( + "shielded withdrawal transition can not be called for identity signing".to_string(), + )), } }; ($state_transition:expr, $method:ident) => { @@ -333,6 +391,21 @@ macro_rules! call_errorable_method_identity_signed { StateTransition::AddressCreditWithdrawal(_) => Err(ProtocolError::CorruptedCodeExecution( "address credit withdrawal can not be called for identity signing".to_string(), )), + StateTransition::Shield(_) => Err(ProtocolError::CorruptedCodeExecution( + "shield transition can not be called for identity signing".to_string(), + )), + StateTransition::ShieldedTransfer(_) => Err(ProtocolError::CorruptedCodeExecution( + "shielded transfer transition can not be called for identity signing".to_string(), + )), + StateTransition::Unshield(_) => Err(ProtocolError::CorruptedCodeExecution( + "unshield transition can not be called for identity signing".to_string(), + )), + StateTransition::ShieldFromAssetLock(_) => Err(ProtocolError::CorruptedCodeExecution( + "shield from asset lock transition can not be called for identity signing".to_string(), + )), + StateTransition::ShieldedWithdrawal(_) => Err(ProtocolError::CorruptedCodeExecution( + "shielded withdrawal transition can not be called for identity signing".to_string(), + )), } }; } @@ -371,6 +444,11 @@ pub enum StateTransition { AddressFundsTransfer(AddressFundsTransferTransition), AddressFundingFromAssetLock(AddressFundingFromAssetLockTransition), AddressCreditWithdrawal(AddressCreditWithdrawalTransition), + Shield(ShieldTransition), + ShieldedTransfer(ShieldedTransferTransition), + Unshield(UnshieldTransition), + ShieldFromAssetLock(ShieldFromAssetLockTransition), + ShieldedWithdrawal(ShieldedWithdrawalTransition), } impl OptionallyAssetLockProved for StateTransition { @@ -378,6 +456,7 @@ impl OptionallyAssetLockProved for StateTransition { match self { StateTransition::IdentityCreate(st) => st.optional_asset_lock_proof(), StateTransition::IdentityTopUp(st) => st.optional_asset_lock_proof(), + StateTransition::ShieldFromAssetLock(st) => st.optional_asset_lock_proof(), _ => None, } } @@ -453,13 +532,24 @@ impl StateTransition { | StateTransition::AddressFundsTransfer(_) | StateTransition::AddressFundingFromAssetLock(_) | StateTransition::AddressCreditWithdrawal(_) => 11..=LATEST_VERSION, + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => 12..=LATEST_VERSION, } } pub fn is_identity_signed(&self) -> bool { !matches!( self, - StateTransition::IdentityCreate(_) | StateTransition::IdentityTopUp(_) + StateTransition::IdentityCreate(_) + | StateTransition::IdentityTopUp(_) + | StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) ) } @@ -477,6 +567,9 @@ impl StateTransition { StateTransition::AddressFundingFromAssetLock(st) => { st.calculate_min_required_fee(platform_version) } + StateTransition::ShieldFromAssetLock(st) => { + st.calculate_min_required_fee(platform_version) + } st => Err(ProtocolError::CorruptedCodeExecution(format!("{} is not an asset lock transaction, but we are calling required_asset_lock_balance_for_processing_start", st.name()))), } } @@ -556,6 +649,11 @@ impl StateTransition { Self::AddressFundsTransfer(_) => "AddressFundsTransfer".to_string(), Self::AddressFundingFromAssetLock(_) => "AddressFundingFromAssetLock".to_string(), Self::AddressCreditWithdrawal(_) => "AddressCreditWithdrawal".to_string(), + Self::Shield(_) => "Shield".to_string(), + Self::ShieldedTransfer(_) => "ShieldedTransfer".to_string(), + Self::Unshield(_) => "Unshield".to_string(), + Self::ShieldFromAssetLock(_) => "ShieldFromAssetLock".to_string(), + Self::ShieldedWithdrawal(_) => "ShieldedWithdrawal".to_string(), } } @@ -577,6 +675,11 @@ impl StateTransition { StateTransition::AddressFundsTransfer(_) => None, StateTransition::AddressFundingFromAssetLock(st) => Some(st.signature()), StateTransition::AddressCreditWithdrawal(_) => None, + StateTransition::Shield(_) => None, + StateTransition::ShieldedTransfer(_) => None, + StateTransition::Unshield(_) => None, + StateTransition::ShieldFromAssetLock(st) => Some(st.signature()), + StateTransition::ShieldedWithdrawal(_) => None, } } @@ -587,6 +690,11 @@ impl StateTransition { StateTransition::IdentityTopUpFromAddresses(st) => st.inputs().len() as u16, StateTransition::AddressFundsTransfer(st) => st.inputs().len() as u16, StateTransition::AddressCreditWithdrawal(st) => st.inputs().len() as u16, + StateTransition::Shield(st) => st.inputs().len() as u16, + StateTransition::ShieldedTransfer(_) => 0, + StateTransition::Unshield(_) => 0, + StateTransition::ShieldFromAssetLock(_) => 0, + StateTransition::ShieldedWithdrawal(_) => 0, _ => 1, } } @@ -655,6 +763,11 @@ impl StateTransition { StateTransition::AddressFundsTransfer(_) => None, StateTransition::AddressFundingFromAssetLock(_) => None, StateTransition::AddressCreditWithdrawal(_) => None, + StateTransition::Shield(_) => None, + StateTransition::ShieldedTransfer(_) => None, + StateTransition::Unshield(_) => None, + StateTransition::ShieldFromAssetLock(_) => None, + StateTransition::ShieldedWithdrawal(_) => None, } } @@ -676,6 +789,11 @@ impl StateTransition { StateTransition::AddressFundsTransfer(st) => Some(st.inputs()), StateTransition::AddressFundingFromAssetLock(st) => Some(st.inputs()), StateTransition::AddressCreditWithdrawal(st) => Some(st.inputs()), + StateTransition::Shield(st) => Some(st.inputs()), + StateTransition::ShieldedTransfer(_) => None, + StateTransition::Unshield(_) => None, + StateTransition::ShieldFromAssetLock(_) => None, + StateTransition::ShieldedWithdrawal(_) => None, } } @@ -734,11 +852,19 @@ impl StateTransition { } StateTransition::IdentityCreateFromAddresses(_) | StateTransition::IdentityTopUpFromAddresses(_) - | StateTransition::AddressFundsTransfer(_) => false, + | StateTransition::AddressFundsTransfer(_) + | StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldedWithdrawal(_) => false, StateTransition::AddressFundingFromAssetLock(st) => { st.set_signature(signature); true } + StateTransition::ShieldFromAssetLock(st) => { + st.set_signature(signature); + true + } StateTransition::AddressCreditWithdrawal(_) => false, } } @@ -891,6 +1017,34 @@ impl StateTransition { .to_string(), )) } + StateTransition::Shield(_) => { + return Err(ProtocolError::CorruptedCodeExecution( + "shield transition can not be called for identity signing".to_string(), + )) + } + StateTransition::ShieldedTransfer(_) => { + return Err(ProtocolError::CorruptedCodeExecution( + "shielded transfer transition can not be called for identity signing" + .to_string(), + )) + } + StateTransition::Unshield(_) => { + return Err(ProtocolError::CorruptedCodeExecution( + "unshield transition can not be called for identity signing".to_string(), + )) + } + StateTransition::ShieldFromAssetLock(_) => { + return Err(ProtocolError::CorruptedCodeExecution( + "shield from asset lock transition can not be called for identity signing" + .to_string(), + )) + } + StateTransition::ShieldedWithdrawal(_) => { + return Err(ProtocolError::CorruptedCodeExecution( + "shielded withdrawal transition can not be called for identity signing" + .to_string(), + )) + } } let data = self.signable_bytes()?; self.set_signature(signer.sign(identity_public_key, data.as_slice())?); @@ -1228,6 +1382,19 @@ impl StateTransitionStructureValidation for StateTransition { StateTransition::AddressCreditWithdrawal(transition) => { transition.validate_structure(platform_version) } + StateTransition::Shield(transition) => transition.validate_structure(platform_version), + StateTransition::ShieldedTransfer(transition) => { + transition.validate_structure(platform_version) + } + StateTransition::Unshield(transition) => { + transition.validate_structure(platform_version) + } + StateTransition::ShieldFromAssetLock(transition) => { + transition.validate_structure(platform_version) + } + StateTransition::ShieldedWithdrawal(transition) => { + transition.validate_structure(platform_version) + } } } } diff --git a/packages/rs-dpp/src/state_transition/proof_result.rs b/packages/rs-dpp/src/state_transition/proof_result.rs index 9500e824e7b..d36e14e0380 100644 --- a/packages/rs-dpp/src/state_transition/proof_result.rs +++ b/packages/rs-dpp/src/state_transition/proof_result.rs @@ -1,4 +1,5 @@ use crate::address_funds::PlatformAddress; +use crate::asset_lock::StoredAssetLockInfo; use crate::balances::credits::TokenAmount; use crate::data_contract::group::GroupSumPower; use crate::data_contract::DataContract; @@ -55,4 +56,15 @@ pub enum StateTransitionProofResult { PartialIdentity, BTreeMap>, ), + #[cfg_attr(feature = "state-transition-serde-conversion", serde(skip))] + VerifiedAssetLockConsumed(StoredAssetLockInfo), + VerifiedShieldedNullifiers(Vec<(Vec, bool)>), + VerifiedShieldedNullifiersWithAddressInfos( + Vec<(Vec, bool)>, + BTreeMap>, + ), + VerifiedShieldedNullifiersWithWithdrawalDocument( + Vec<(Vec, bool)>, + BTreeMap>, + ), } diff --git a/packages/rs-dpp/src/state_transition/state_transition_types.rs b/packages/rs-dpp/src/state_transition/state_transition_types.rs index f8ffaba25d7..e38071b4b70 100644 --- a/packages/rs-dpp/src/state_transition/state_transition_types.rs +++ b/packages/rs-dpp/src/state_transition/state_transition_types.rs @@ -35,6 +35,11 @@ pub enum StateTransitionType { AddressFundsTransfer = 12, AddressFundingFromAssetLock = 13, AddressCreditWithdrawal = 14, + Shield = 15, + ShieldedTransfer = 16, + Unshield = 17, + ShieldFromAssetLock = 18, + ShieldedWithdrawal = 19, } impl std::fmt::Display for StateTransitionType { diff --git a/packages/rs-dpp/src/state_transition/state_transitions/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/mod.rs index cc748f9bb4d..1ee962d0587 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/mod.rs @@ -9,3 +9,6 @@ pub use address_funds::*; pub use contract::*; pub use document::*; pub use identity::*; + +pub mod shielded; +pub use shielded::*; 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 new file mode 100644 index 00000000000..c18f5084c89 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/common_validation.rs @@ -0,0 +1,51 @@ +use crate::consensus::basic::state_transition::{ + ShieldedEmptyProofError, ShieldedNoActionsError, ShieldedTooManyActionsError, + ShieldedZeroAnchorError, +}; +use crate::consensus::basic::BasicError; +use crate::shielded::SerializedAction; +use crate::validation::SimpleConsensusValidationResult; + +/// Validate that the actions list is not empty and does not exceed the maximum. +pub fn validate_actions_count( + actions: &[SerializedAction], + max_actions: u16, +) -> Option { + if actions.is_empty() { + Some(SimpleConsensusValidationResult::new_with_error( + BasicError::ShieldedNoActionsError(ShieldedNoActionsError::new()).into(), + )) + } else if actions.len() > max_actions as usize { + Some(SimpleConsensusValidationResult::new_with_error( + BasicError::ShieldedTooManyActionsError(ShieldedTooManyActionsError::new( + actions.len().min(u16::MAX as usize) as u16, + max_actions, + )) + .into(), + )) + } else { + None + } +} + +/// Validate that the proof is not empty. +pub fn validate_proof_not_empty(proof: &[u8]) -> Option { + if proof.is_empty() { + Some(SimpleConsensusValidationResult::new_with_error( + BasicError::ShieldedEmptyProofError(ShieldedEmptyProofError::new()).into(), + )) + } else { + None + } +} + +/// Validate that the anchor is not all zeros (for transitions that consume notes). +pub fn validate_anchor_not_zero(anchor: &[u8; 32]) -> Option { + if *anchor == [0u8; 32] { + Some(SimpleConsensusValidationResult::new_with_error( + BasicError::ShieldedZeroAnchorError(ShieldedZeroAnchorError::new()).into(), + )) + } else { + None + } +} 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 new file mode 100644 index 00000000000..cb79e3f690f --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/mod.rs @@ -0,0 +1,6 @@ +pub(crate) mod common_validation; +pub mod shield_from_asset_lock_transition; +pub mod shield_transition; +pub mod shielded_transfer_transition; +pub mod shielded_withdrawal_transition; +pub mod unshield_transition; diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/mod.rs new file mode 100644 index 00000000000..5b5288351aa --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/mod.rs @@ -0,0 +1,61 @@ +mod v0; + +pub use v0::*; + +#[cfg(feature = "state-transition-signing")] +use crate::prelude::AssetLockProof; +#[cfg(feature = "state-transition-signing")] +use crate::shielded::SerializedAction; +use crate::state_transition::shield_from_asset_lock_transition::ShieldFromAssetLockTransition; +#[cfg(feature = "state-transition-signing")] +use crate::{ + prelude::UserFeeIncrease, + state_transition::{ + shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0, StateTransition, + }, + ProtocolError, +}; +#[cfg(feature = "state-transition-signing")] +use platform_version::version::PlatformVersion; + +impl ShieldFromAssetLockTransitionMethodsV0 for ShieldFromAssetLockTransition { + #[cfg(feature = "state-transition-signing")] + fn try_from_asset_lock_with_bundle( + asset_lock_proof: AssetLockProof, + asset_lock_proof_private_key: &[u8], + actions: Vec, + flags: u8, + value_balance: i64, + anchor: [u8; 32], + proof: Vec, + binding_signature: [u8; 64], + user_fee_increase: UserFeeIncrease, + platform_version: &PlatformVersion, + ) -> Result { + match platform_version + .dpp + .state_transition_serialization_versions + .shield_from_asset_lock_state_transition + .default_current_version + { + 0 => ShieldFromAssetLockTransitionV0::try_from_asset_lock_with_bundle( + asset_lock_proof, + asset_lock_proof_private_key, + actions, + flags, + value_balance, + anchor, + proof, + binding_signature, + user_fee_increase, + platform_version, + ), + version => Err(ProtocolError::UnknownVersionMismatch { + method: "ShieldFromAssetLockTransition::try_from_asset_lock_with_bundle" + .to_string(), + known_versions: vec![0], + received: version, + }), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/v0/mod.rs new file mode 100644 index 00000000000..49fbb3f59d4 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/v0/mod.rs @@ -0,0 +1,31 @@ +#[cfg(feature = "state-transition-signing")] +use crate::prelude::AssetLockProof; +#[cfg(feature = "state-transition-signing")] +use crate::shielded::SerializedAction; +use crate::state_transition::StateTransitionType; +#[cfg(feature = "state-transition-signing")] +use crate::{prelude::UserFeeIncrease, state_transition::StateTransition, ProtocolError}; +#[cfg(feature = "state-transition-signing")] +use platform_version::version::PlatformVersion; + +pub trait ShieldFromAssetLockTransitionMethodsV0 { + #[cfg(feature = "state-transition-signing")] + #[allow(clippy::too_many_arguments)] + fn try_from_asset_lock_with_bundle( + asset_lock_proof: AssetLockProof, + asset_lock_proof_private_key: &[u8], + actions: Vec, + flags: u8, + value_balance: i64, + anchor: [u8; 32], + proof: Vec, + binding_signature: [u8; 64], + user_fee_increase: UserFeeIncrease, + platform_version: &PlatformVersion, + ) -> Result; + + /// Get State Transition Type + fn get_type() -> StateTransitionType { + StateTransitionType::ShieldFromAssetLock + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/mod.rs new file mode 100644 index 00000000000..4d496b73de7 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/mod.rs @@ -0,0 +1,61 @@ +pub mod methods; +mod proved; +mod state_transition_estimated_fee_validation; +mod state_transition_like; +mod state_transition_validation; +pub mod v0; +mod version; + +use crate::state_transition::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0; +use crate::state_transition::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0Signable; +use crate::state_transition::StateTransitionFieldTypes; + +pub type ShieldFromAssetLockTransitionLatest = ShieldFromAssetLockTransitionV0; + +use crate::ProtocolError; +use bincode::{Decode, Encode}; +use derive_more::From; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize, PlatformSignable}; +use platform_versioning::PlatformVersioned; +#[cfg(feature = "state-transition-serde-conversion")] +use serde::{Deserialize, Serialize}; + +#[derive( + Debug, + Clone, + Encode, + Decode, + PlatformDeserialize, + PlatformSerialize, + PlatformSignable, + PlatformVersioned, + From, + PartialEq, +)] +#[cfg_attr( + feature = "state-transition-serde-conversion", + derive(Serialize, Deserialize), + serde(tag = "$version") +)] +#[platform_serialize(unversioned)] //versioned directly, no need to use platform_version +#[platform_version_path_bounds( + "dpp.state_transition_serialization_versions.shield_from_asset_lock_state_transition" +)] +pub enum ShieldFromAssetLockTransition { + #[cfg_attr(feature = "state-transition-serde-conversion", serde(rename = "0"))] + V0(ShieldFromAssetLockTransitionV0), +} + +impl StateTransitionFieldTypes for ShieldFromAssetLockTransition { + fn signature_property_paths() -> Vec<&'static str> { + vec![] + } + + fn identifiers_property_paths() -> Vec<&'static str> { + vec![] + } + + fn binary_property_paths() -> Vec<&'static str> { + vec![] + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/proved.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/proved.rs new file mode 100644 index 00000000000..4c06ae7ea68 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/proved.rs @@ -0,0 +1,27 @@ +use crate::identity::state_transition::{AssetLockProved, OptionallyAssetLockProved}; +use crate::prelude::AssetLockProof; +use crate::state_transition::shield_from_asset_lock_transition::ShieldFromAssetLockTransition; +use crate::ProtocolError; + +impl OptionallyAssetLockProved for ShieldFromAssetLockTransition { + fn optional_asset_lock_proof(&self) -> Option<&AssetLockProof> { + Some(self.asset_lock_proof()) + } +} + +impl AssetLockProved for ShieldFromAssetLockTransition { + fn set_asset_lock_proof( + &mut self, + asset_lock_proof: AssetLockProof, + ) -> Result<(), ProtocolError> { + match self { + ShieldFromAssetLockTransition::V0(v0) => v0.set_asset_lock_proof(asset_lock_proof), + } + } + + fn asset_lock_proof(&self) -> &AssetLockProof { + match self { + ShieldFromAssetLockTransition::V0(v0) => v0.asset_lock_proof(), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/state_transition_estimated_fee_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/state_transition_estimated_fee_validation.rs new file mode 100644 index 00000000000..1141003afc6 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/state_transition_estimated_fee_validation.rs @@ -0,0 +1,22 @@ +use crate::balances::credits::CREDITS_PER_DUFF; +use crate::fee::Credits; +use crate::state_transition::shield_from_asset_lock_transition::ShieldFromAssetLockTransition; +use crate::state_transition::StateTransitionEstimatedFeeValidation; +use crate::ProtocolError; +use platform_version::version::PlatformVersion; + +impl StateTransitionEstimatedFeeValidation for ShieldFromAssetLockTransition { + fn calculate_min_required_fee( + &self, + platform_version: &PlatformVersion, + ) -> Result { + let asset_lock_base_cost = platform_version + .dpp + .state_transitions + .identities + .asset_locks + .required_asset_lock_duff_balance_for_processing_start_for_address_funding + * CREDITS_PER_DUFF; + Ok(asset_lock_base_cost) + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/state_transition_like.rs new file mode 100644 index 00000000000..51844628ca7 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/state_transition_like.rs @@ -0,0 +1,75 @@ +use crate::prelude::UserFeeIncrease; +use crate::state_transition::shield_from_asset_lock_transition::ShieldFromAssetLockTransition; +use crate::state_transition::{ + StateTransitionLike, StateTransitionSingleSigned, StateTransitionType, +}; +use crate::version::FeatureVersion; +use platform_value::{BinaryData, Identifier}; + +impl StateTransitionLike for ShieldFromAssetLockTransition { + /// Returns IDs of the modified data + fn modified_data_ids(&self) -> Vec { + match self { + ShieldFromAssetLockTransition::V0(transition) => transition.modified_data_ids(), + } + } + + fn state_transition_protocol_version(&self) -> FeatureVersion { + match self { + ShieldFromAssetLockTransition::V0(_) => 0, + } + } + + /// returns the type of State Transition + fn state_transition_type(&self) -> StateTransitionType { + match self { + ShieldFromAssetLockTransition::V0(transition) => transition.state_transition_type(), + } + } + + /// returns the fee multiplier + fn user_fee_increase(&self) -> UserFeeIncrease { + match self { + ShieldFromAssetLockTransition::V0(transition) => transition.user_fee_increase(), + } + } + + /// set a fee multiplier + fn set_user_fee_increase(&mut self, user_fee_increase: UserFeeIncrease) { + match self { + ShieldFromAssetLockTransition::V0(transition) => { + transition.set_user_fee_increase(user_fee_increase) + } + } + } + + fn unique_identifiers(&self) -> Vec { + match self { + ShieldFromAssetLockTransition::V0(transition) => transition.unique_identifiers(), + } + } +} + +impl StateTransitionSingleSigned for ShieldFromAssetLockTransition { + /// returns the signature as a byte-array + fn signature(&self) -> &BinaryData { + match self { + ShieldFromAssetLockTransition::V0(transition) => transition.signature(), + } + } + + /// set a new signature + fn set_signature(&mut self, signature: BinaryData) { + match self { + ShieldFromAssetLockTransition::V0(transition) => transition.set_signature(signature), + } + } + + fn set_signature_bytes(&mut self, signature: Vec) { + match self { + ShieldFromAssetLockTransition::V0(transition) => { + transition.set_signature_bytes(signature) + } + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/state_transition_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/state_transition_validation.rs new file mode 100644 index 00000000000..bf6bf059c24 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/state_transition_validation.rs @@ -0,0 +1,15 @@ +use crate::state_transition::shield_from_asset_lock_transition::ShieldFromAssetLockTransition; +use crate::state_transition::StateTransitionStructureValidation; +use crate::validation::SimpleConsensusValidationResult; +use platform_version::version::PlatformVersion; + +impl StateTransitionStructureValidation for ShieldFromAssetLockTransition { + fn validate_structure( + &self, + platform_version: &PlatformVersion, + ) -> SimpleConsensusValidationResult { + match self { + ShieldFromAssetLockTransition::V0(v0) => v0.validate_structure(platform_version), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs new file mode 100644 index 00000000000..716468a9bf0 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs @@ -0,0 +1,107 @@ +mod proved; +mod state_transition_like; +mod state_transition_validation; +mod types; +pub(super) mod v0_methods; +mod version; + +use crate::identity::state_transition::asset_lock_proof::AssetLockProof; +use crate::prelude::UserFeeIncrease; +use crate::shielded::SerializedAction; +use crate::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize, PlatformSignable}; +use platform_value::BinaryData; +#[cfg(feature = "state-transition-serde-conversion")] +use serde::{Deserialize, Serialize}; + +#[derive( + Debug, + Clone, + Encode, + Decode, + PlatformSerialize, + PlatformDeserialize, + PlatformSignable, + PartialEq, +)] +#[cfg_attr( + feature = "state-transition-serde-conversion", + derive(Serialize, Deserialize), + serde(rename_all = "camelCase") +)] +#[platform_serialize(unversioned)] +pub struct ShieldFromAssetLockTransitionV0 { + /// Asset lock proof from L1 + pub asset_lock_proof: AssetLockProof, + /// Orchard actions (spend-output pairs) + pub actions: Vec, + /// Bundle flags (spends_enabled | outputs_enabled) + pub flags: u8, + /// Net value flowing into the shielded pool (must be negative for shielding) + pub value_balance: i64, + /// Merkle root of the commitment tree at time of bundle creation + pub anchor: [u8; 32], + /// Halo2 proof bytes + pub proof: Vec, + /// RedPallas binding signature + #[cfg_attr( + feature = "state-transition-serde-conversion", + serde(with = "crate::shielded::serde_bytes_64") + )] + pub binding_signature: [u8; 64], + /// Fee multiplier + pub user_fee_increase: UserFeeIncrease, + /// ECDSA signature over the signable bytes (excluded from sig hash) + #[platform_signable(exclude_from_sig_hash)] + pub signature: BinaryData, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::identity::state_transition::asset_lock_proof::chain::ChainAssetLockProof; + use crate::serialization::{PlatformDeserializable, PlatformSerializable}; + use dashcore::OutPoint; + use std::fmt::Debug; + + fn test_round_trip( + transition: T, + ) where + ::Error: std::fmt::Debug, + { + let serialized = T::serialize_to_bytes(&transition).expect("expected to serialize"); + let deserialized = + T::deserialize_from_bytes(serialized.as_slice()).expect("expected to deserialize"); + assert_eq!(transition, deserialized); + } + + #[test] + fn test_shield_from_asset_lock_transition_v0_serialization_round_trip() { + let chain_proof = ChainAssetLockProof { + core_chain_locked_height: 100, + out_point: OutPoint::from([11u8; 36]), + }; + + let transition = ShieldFromAssetLockTransitionV0 { + asset_lock_proof: AssetLockProof::Chain(chain_proof), + actions: vec![SerializedAction { + nullifier: [1u8; 32], + rk: [2u8; 32], + cmx: [3u8; 32], + encrypted_note: vec![4u8; 692], + cv_net: [5u8; 32], + spend_auth_sig: [6u8; 64], + }], + flags: 0u8, + value_balance: -1000i64, + anchor: [7u8; 32], + proof: vec![8u8; 100], + binding_signature: [9u8; 64], + user_fee_increase: 0u16, + signature: BinaryData::new(vec![10u8; 65]), + }; + + test_round_trip(transition); + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/proved.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/proved.rs new file mode 100644 index 00000000000..9eccf52473e --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/proved.rs @@ -0,0 +1,18 @@ +use crate::identity::state_transition::AssetLockProved; +use crate::prelude::AssetLockProof; +use crate::state_transition::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0; +use crate::ProtocolError; + +impl AssetLockProved for ShieldFromAssetLockTransitionV0 { + fn set_asset_lock_proof( + &mut self, + asset_lock_proof: AssetLockProof, + ) -> Result<(), ProtocolError> { + self.asset_lock_proof = asset_lock_proof; + Ok(()) + } + + fn asset_lock_proof(&self) -> &AssetLockProof { + &self.asset_lock_proof + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/state_transition_like.rs new file mode 100644 index 00000000000..6314d80b308 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/state_transition_like.rs @@ -0,0 +1,63 @@ +use platform_value::BinaryData; + +use crate::prelude::UserFeeIncrease; +use crate::state_transition::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0; +use crate::state_transition::shield_from_asset_lock_transition::ShieldFromAssetLockTransition; +use crate::state_transition::{StateTransition, StateTransitionSingleSigned}; +use crate::version::FeatureVersion; +use crate::{ + prelude::Identifier, + state_transition::{StateTransitionLike, StateTransitionType}, +}; + +impl From for StateTransition { + fn from(value: ShieldFromAssetLockTransitionV0) -> Self { + let transition: ShieldFromAssetLockTransition = value.into(); + transition.into() + } +} + +impl StateTransitionLike for ShieldFromAssetLockTransitionV0 { + fn state_transition_protocol_version(&self) -> FeatureVersion { + 0 + } + + /// returns the type of State Transition + fn state_transition_type(&self) -> StateTransitionType { + StateTransitionType::ShieldFromAssetLock + } + + /// Returns IDs of modified data (none for shielded transitions) + fn modified_data_ids(&self) -> Vec { + vec![] + } + + /// Returns unique identifiers based on the cmx values from actions + fn unique_identifiers(&self) -> Vec { + self.actions.iter().map(|a| hex::encode(a.cmx)).collect() + } + + fn user_fee_increase(&self) -> UserFeeIncrease { + self.user_fee_increase + } + + fn set_user_fee_increase(&mut self, user_fee_increase: UserFeeIncrease) { + self.user_fee_increase = user_fee_increase + } +} + +impl StateTransitionSingleSigned for ShieldFromAssetLockTransitionV0 { + /// returns the signature as a byte-array + fn signature(&self) -> &BinaryData { + &self.signature + } + + /// set a new signature + fn set_signature(&mut self, signature: BinaryData) { + self.signature = signature + } + + fn set_signature_bytes(&mut self, signature: Vec) { + self.signature = BinaryData::new(signature) + } +} 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 new file mode 100644 index 00000000000..cd9c691f79b --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/state_transition_validation.rs @@ -0,0 +1,50 @@ +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, +}; +use crate::state_transition::StateTransitionStructureValidation; +use crate::validation::SimpleConsensusValidationResult; +use platform_version::version::PlatformVersion; + +impl StateTransitionStructureValidation for ShieldFromAssetLockTransitionV0 { + fn validate_structure( + &self, + platform_version: &PlatformVersion, + ) -> SimpleConsensusValidationResult { + // Actions count must be in [1, max] + if let Some(err) = validate_actions_count( + &self.actions, + platform_version + .system_limits + .max_shielded_transition_actions, + ) { + return err; + } + + // value_balance must be negative (credits flowing into pool) + if self.value_balance >= 0 { + return SimpleConsensusValidationResult::new_with_error( + BasicError::ShieldedInvalidValueBalanceError( + ShieldedInvalidValueBalanceError::new( + "shield_from_asset_lock value_balance must be negative".to_string(), + ), + ) + .into(), + ); + } + + // Proof must not be empty + if let Some(err) = validate_proof_not_empty(&self.proof) { + return err; + } + + // Anchor must not be all zeros + if let Some(err) = validate_anchor_not_zero(&self.anchor) { + return err; + } + + SimpleConsensusValidationResult::new() + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/types.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/types.rs new file mode 100644 index 00000000000..09acbb4fe11 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/types.rs @@ -0,0 +1,16 @@ +use crate::state_transition::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0; +use crate::state_transition::StateTransitionFieldTypes; + +impl StateTransitionFieldTypes for ShieldFromAssetLockTransitionV0 { + fn signature_property_paths() -> Vec<&'static str> { + vec![] + } + + fn identifiers_property_paths() -> Vec<&'static str> { + vec![] + } + + fn binary_property_paths() -> Vec<&'static str> { + vec![] + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/v0_methods.rs new file mode 100644 index 00000000000..baafa61a098 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/v0_methods.rs @@ -0,0 +1,53 @@ +#[cfg(feature = "state-transition-signing")] +use crate::prelude::AssetLockProof; +#[cfg(feature = "state-transition-signing")] +use crate::serialization::Signable; +#[cfg(feature = "state-transition-signing")] +use crate::shielded::SerializedAction; +use crate::state_transition::shield_from_asset_lock_transition::methods::ShieldFromAssetLockTransitionMethodsV0; +use crate::state_transition::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0; +#[cfg(feature = "state-transition-signing")] +use crate::{prelude::UserFeeIncrease, state_transition::StateTransition, ProtocolError}; +#[cfg(feature = "state-transition-signing")] +use dashcore::signer; +#[cfg(feature = "state-transition-signing")] +use platform_version::version::PlatformVersion; + +impl ShieldFromAssetLockTransitionMethodsV0 for ShieldFromAssetLockTransitionV0 { + #[cfg(feature = "state-transition-signing")] + fn try_from_asset_lock_with_bundle( + asset_lock_proof: AssetLockProof, + asset_lock_proof_private_key: &[u8], + actions: Vec, + flags: u8, + value_balance: i64, + anchor: [u8; 32], + proof: Vec, + binding_signature: [u8; 64], + user_fee_increase: UserFeeIncrease, + _platform_version: &PlatformVersion, + ) -> Result { + // Create the unsigned transition + let mut transition = ShieldFromAssetLockTransitionV0 { + asset_lock_proof, + actions, + flags, + value_balance, + anchor, + proof, + binding_signature, + user_fee_increase, + signature: Default::default(), + }; + + // Compute signable bytes (signature field is excluded from sig hash) + let state_transition: StateTransition = transition.clone().into(); + let signable_bytes = state_transition.signable_bytes()?; + + // Sign with the asset lock private key (ECDSA) + let signature = signer::sign(&signable_bytes, asset_lock_proof_private_key)?; + transition.signature = signature.to_vec().into(); + + Ok(transition.into()) + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/version.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/version.rs new file mode 100644 index 00000000000..4ffb283d990 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/version.rs @@ -0,0 +1,9 @@ +use crate::state_transition::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0; +use crate::state_transition::FeatureVersioned; +use crate::version::FeatureVersion; + +impl FeatureVersioned for ShieldFromAssetLockTransitionV0 { + fn feature_version(&self) -> FeatureVersion { + 0 + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/version.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/version.rs new file mode 100644 index 00000000000..949a31fb04a --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/version.rs @@ -0,0 +1,11 @@ +use crate::state_transition::shield_from_asset_lock_transition::ShieldFromAssetLockTransition; +use crate::state_transition::FeatureVersioned; +use crate::version::FeatureVersion; + +impl FeatureVersioned for ShieldFromAssetLockTransition { + fn feature_version(&self) -> FeatureVersion { + match self { + ShieldFromAssetLockTransition::V0(v0) => v0.feature_version(), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/methods/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/methods/mod.rs new file mode 100644 index 00000000000..fa612ecbeb2 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/methods/mod.rs @@ -0,0 +1,66 @@ +mod v0; + +#[cfg(feature = "state-transition-signing")] +use std::collections::BTreeMap; +pub use v0::*; + +#[cfg(feature = "state-transition-signing")] +use crate::address_funds::{AddressFundsFeeStrategy, PlatformAddress}; +#[cfg(feature = "state-transition-signing")] +use crate::fee::Credits; +#[cfg(feature = "state-transition-signing")] +use crate::identity::signer::Signer; +#[cfg(feature = "state-transition-signing")] +use crate::shielded::SerializedAction; +use crate::state_transition::shield_transition::ShieldTransition; +#[cfg(feature = "state-transition-signing")] +use crate::{ + prelude::{AddressNonce, UserFeeIncrease}, + state_transition::{shield_transition::v0::ShieldTransitionV0, StateTransition}, + ProtocolError, +}; +#[cfg(feature = "state-transition-signing")] +use platform_version::version::PlatformVersion; + +impl ShieldTransitionMethodsV0 for ShieldTransition { + #[cfg(feature = "state-transition-signing")] + fn try_from_bundle_with_signer>( + inputs: BTreeMap, + actions: Vec, + flags: u8, + value_balance: i64, + anchor: [u8; 32], + proof: Vec, + binding_signature: [u8; 64], + fee_strategy: AddressFundsFeeStrategy, + signer: &S, + user_fee_increase: UserFeeIncrease, + platform_version: &PlatformVersion, + ) -> Result { + match platform_version + .dpp + .state_transition_serialization_versions + .shield_state_transition + .default_current_version + { + 0 => ShieldTransitionV0::try_from_bundle_with_signer( + inputs, + actions, + flags, + value_balance, + anchor, + proof, + binding_signature, + fee_strategy, + signer, + user_fee_increase, + platform_version, + ), + version => Err(ProtocolError::UnknownVersionMismatch { + method: "ShieldTransition::try_from_bundle_with_signer".to_string(), + known_versions: vec![0], + received: version, + }), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/methods/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/methods/v0/mod.rs new file mode 100644 index 00000000000..49f1d4d29e3 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/methods/v0/mod.rs @@ -0,0 +1,43 @@ +#[cfg(feature = "state-transition-signing")] +use std::collections::BTreeMap; + +#[cfg(feature = "state-transition-signing")] +use crate::address_funds::{AddressFundsFeeStrategy, PlatformAddress}; +#[cfg(feature = "state-transition-signing")] +use crate::fee::Credits; +#[cfg(feature = "state-transition-signing")] +use crate::identity::signer::Signer; +#[cfg(feature = "state-transition-signing")] +use crate::shielded::SerializedAction; +use crate::state_transition::StateTransitionType; +#[cfg(feature = "state-transition-signing")] +use crate::{ + prelude::{AddressNonce, UserFeeIncrease}, + state_transition::StateTransition, + ProtocolError, +}; +#[cfg(feature = "state-transition-signing")] +use platform_version::version::PlatformVersion; + +pub trait ShieldTransitionMethodsV0 { + #[cfg(feature = "state-transition-signing")] + #[allow(clippy::too_many_arguments)] + fn try_from_bundle_with_signer>( + inputs: BTreeMap, + actions: Vec, + flags: u8, + value_balance: i64, + anchor: [u8; 32], + proof: Vec, + binding_signature: [u8; 64], + fee_strategy: AddressFundsFeeStrategy, + signer: &S, + user_fee_increase: UserFeeIncrease, + platform_version: &PlatformVersion, + ) -> Result; + + /// Get State Transition Type + fn get_type() -> StateTransitionType { + StateTransitionType::Shield + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/mod.rs new file mode 100644 index 00000000000..720b637ebfe --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/mod.rs @@ -0,0 +1,63 @@ +pub mod methods; +mod state_transition_estimated_fee_validation; +mod state_transition_like; +mod state_transition_validation; +pub mod v0; +mod version; + +use crate::state_transition::shield_transition::v0::ShieldTransitionV0; +use crate::state_transition::shield_transition::v0::ShieldTransitionV0Signable; +use crate::state_transition::StateTransitionFieldTypes; + +pub type ShieldTransitionLatest = ShieldTransitionV0; + +use crate::identity::state_transition::OptionallyAssetLockProved; +use crate::ProtocolError; +use bincode::{Decode, Encode}; +use derive_more::From; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize, PlatformSignable}; +use platform_versioning::PlatformVersioned; +#[cfg(feature = "state-transition-serde-conversion")] +use serde::{Deserialize, Serialize}; + +#[derive( + Debug, + Clone, + Encode, + Decode, + PlatformDeserialize, + PlatformSerialize, + PlatformSignable, + PlatformVersioned, + From, + PartialEq, +)] +#[cfg_attr( + feature = "state-transition-serde-conversion", + derive(Serialize, Deserialize), + serde(tag = "$version") +)] +#[platform_serialize(unversioned)] //versioned directly, no need to use platform_version +#[platform_version_path_bounds( + "dpp.state_transition_serialization_versions.shield_state_transition" +)] +pub enum ShieldTransition { + #[cfg_attr(feature = "state-transition-serde-conversion", serde(rename = "0"))] + V0(ShieldTransitionV0), +} + +impl OptionallyAssetLockProved for ShieldTransition {} + +impl StateTransitionFieldTypes for ShieldTransition { + fn signature_property_paths() -> Vec<&'static str> { + vec![] + } + + fn identifiers_property_paths() -> Vec<&'static str> { + vec![] + } + + fn binary_property_paths() -> Vec<&'static str> { + vec![] + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/state_transition_estimated_fee_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/state_transition_estimated_fee_validation.rs new file mode 100644 index 00000000000..d8a5d6ce98b --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/state_transition_estimated_fee_validation.rs @@ -0,0 +1,20 @@ +use crate::fee::Credits; +use crate::state_transition::shield_transition::ShieldTransition; +use crate::state_transition::{ + StateTransitionEstimatedFeeValidation, StateTransitionWitnessSigned, +}; +use crate::ProtocolError; +use platform_version::version::PlatformVersion; + +impl StateTransitionEstimatedFeeValidation for ShieldTransition { + fn calculate_min_required_fee( + &self, + platform_version: &PlatformVersion, + ) -> Result { + let min_fees = &platform_version.fee_version.state_transition_min_fees; + let input_count = self.inputs().len(); + Ok(min_fees + .address_funds_transfer_input_cost + .saturating_mul(input_count as u64)) + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/state_transition_like.rs new file mode 100644 index 00000000000..089ae4b2749 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/state_transition_like.rs @@ -0,0 +1,96 @@ +use crate::address_funds::AddressWitness; +use crate::prelude::UserFeeIncrease; +use crate::state_transition::shield_transition::ShieldTransition; +use crate::state_transition::{ + StateTransitionLike, StateTransitionType, StateTransitionWitnessSigned, +}; +use crate::version::FeatureVersion; +use platform_value::Identifier; + +impl StateTransitionLike for ShieldTransition { + /// Returns ID of the created contract + fn modified_data_ids(&self) -> Vec { + match self { + ShieldTransition::V0(transition) => transition.modified_data_ids(), + } + } + + fn state_transition_protocol_version(&self) -> FeatureVersion { + match self { + ShieldTransition::V0(_) => 0, + } + } + /// returns the type of State Transition + fn state_transition_type(&self) -> StateTransitionType { + match self { + ShieldTransition::V0(transition) => transition.state_transition_type(), + } + } + + /// returns the fee multiplier + fn user_fee_increase(&self) -> UserFeeIncrease { + match self { + ShieldTransition::V0(transition) => transition.user_fee_increase(), + } + } + /// set a fee multiplier + fn set_user_fee_increase(&mut self, user_fee_increase: UserFeeIncrease) { + match self { + ShieldTransition::V0(transition) => transition.set_user_fee_increase(user_fee_increase), + } + } + + fn unique_identifiers(&self) -> Vec { + match self { + ShieldTransition::V0(transition) => transition.unique_identifiers(), + } + } +} + +impl StateTransitionWitnessSigned for ShieldTransition { + fn inputs( + &self, + ) -> &std::collections::BTreeMap< + crate::address_funds::PlatformAddress, + (crate::prelude::AddressNonce, crate::fee::Credits), + > { + match self { + ShieldTransition::V0(transition) => transition.inputs(), + } + } + + fn inputs_mut( + &mut self, + ) -> &mut std::collections::BTreeMap< + crate::address_funds::PlatformAddress, + (crate::prelude::AddressNonce, crate::fee::Credits), + > { + match self { + ShieldTransition::V0(transition) => transition.inputs_mut(), + } + } + + fn set_inputs( + &mut self, + inputs: std::collections::BTreeMap< + crate::address_funds::PlatformAddress, + (crate::prelude::AddressNonce, crate::fee::Credits), + >, + ) { + match self { + ShieldTransition::V0(transition) => transition.set_inputs(inputs), + } + } + + fn witnesses(&self) -> &Vec { + match self { + ShieldTransition::V0(transition) => transition.witnesses(), + } + } + + fn set_witnesses(&mut self, witnesses: Vec) { + match self { + ShieldTransition::V0(transition) => transition.set_witnesses(witnesses), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/state_transition_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/state_transition_validation.rs new file mode 100644 index 00000000000..71d92296f55 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/state_transition_validation.rs @@ -0,0 +1,19 @@ +use crate::state_transition::shield_transition::ShieldTransition; +use crate::state_transition::{ + StateTransitionStructureValidation, StateTransitionWitnessValidation, +}; +use crate::validation::SimpleConsensusValidationResult; +use platform_version::version::PlatformVersion; + +impl StateTransitionStructureValidation for ShieldTransition { + fn validate_structure( + &self, + platform_version: &PlatformVersion, + ) -> SimpleConsensusValidationResult { + match self { + ShieldTransition::V0(v0) => v0.validate_structure(platform_version), + } + } +} + +impl StateTransitionWitnessValidation for ShieldTransition {} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/mod.rs new file mode 100644 index 00000000000..1a62c9b5595 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/mod.rs @@ -0,0 +1,113 @@ +mod state_transition_like; +mod state_transition_validation; +mod types; +#[cfg(feature = "state-transition-signing")] +pub(super) mod v0_methods; +mod version; + +use std::collections::BTreeMap; + +use crate::address_funds::{AddressFundsFeeStrategy, AddressWitness, PlatformAddress}; +use crate::fee::Credits; +use crate::prelude::{AddressNonce, UserFeeIncrease}; +use crate::shielded::SerializedAction; +use crate::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize, PlatformSignable}; +#[cfg(feature = "state-transition-serde-conversion")] +use serde::{Deserialize, Serialize}; + +#[derive( + Debug, + Clone, + Encode, + Decode, + PlatformSerialize, + PlatformDeserialize, + PlatformSignable, + PartialEq, +)] +#[cfg_attr( + feature = "state-transition-serde-conversion", + derive(Serialize, Deserialize), + serde(rename_all = "camelCase") +)] +#[platform_serialize(unversioned)] +pub struct ShieldTransitionV0 { + /// Address inputs funding the shield (address -> nonce + max contribution). + /// The total across all inputs must cover |value_balance| + fees. + /// Excess credits remain in the source addresses. + pub inputs: BTreeMap, + /// Orchard actions (spend-output pairs) + pub actions: Vec, + /// Bundle flags (spends_enabled | outputs_enabled) + pub flags: u8, + /// Net value flowing into/out of the shielded pool + pub value_balance: i64, + /// Merkle root of the commitment tree at time of bundle creation + pub anchor: [u8; 32], + /// Halo2 proof bytes + pub proof: Vec, + /// RedPallas binding signature + #[cfg_attr( + feature = "state-transition-serde-conversion", + serde(with = "crate::shielded::serde_bytes_64") + )] + pub binding_signature: [u8; 64], + /// Fee payment strategy + pub fee_strategy: AddressFundsFeeStrategy, + /// Fee multiplier + pub user_fee_increase: UserFeeIncrease, + /// Address witness signatures (excluded from sig hash) + #[platform_signable(exclude_from_sig_hash)] + pub input_witnesses: Vec, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::serialization::{PlatformDeserializable, PlatformSerializable}; + use std::fmt::Debug; + + fn test_round_trip( + transition: T, + ) where + ::Error: std::fmt::Debug, + { + let serialized = T::serialize_to_bytes(&transition).expect("expected to serialize"); + let deserialized = + T::deserialize_from_bytes(serialized.as_slice()).expect("expected to deserialize"); + assert_eq!(transition, deserialized); + } + + #[test] + fn test_shield_transition_v0_serialization_round_trip() { + let mut inputs = BTreeMap::new(); + let address = PlatformAddress::P2pkh([ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + ]); + inputs.insert(address, (1u32, 1000u64)); // nonce, credits + + let transition = ShieldTransitionV0 { + inputs, + actions: vec![SerializedAction { + nullifier: [1u8; 32], + rk: [2u8; 32], + cmx: [3u8; 32], + encrypted_note: vec![4u8; 692], + cv_net: [5u8; 32], + spend_auth_sig: [6u8; 64], + }], + flags: 0u8, + value_balance: -1000i64, + anchor: [7u8; 32], + proof: vec![8u8; 100], + binding_signature: [9u8; 64], + fee_strategy: vec![], + user_fee_increase: 0u16, + input_witnesses: vec![], + }; + + test_round_trip(transition); + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/state_transition_like.rs new file mode 100644 index 00000000000..2fbe21a3f39 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/state_transition_like.rs @@ -0,0 +1,89 @@ +use crate::address_funds::AddressWitness; +use crate::prelude::UserFeeIncrease; +use crate::state_transition::shield_transition::v0::ShieldTransitionV0; +use crate::state_transition::shield_transition::ShieldTransition; +use crate::{ + prelude::Identifier, + state_transition::{StateTransitionLike, StateTransitionType}, +}; + +use crate::state_transition::StateTransitionType::Shield; +use crate::state_transition::{StateTransition, StateTransitionWitnessSigned}; +use crate::version::FeatureVersion; + +impl From for StateTransition { + fn from(value: ShieldTransitionV0) -> Self { + let shield_transition: ShieldTransition = value.into(); + shield_transition.into() + } +} + +impl StateTransitionLike for ShieldTransitionV0 { + fn state_transition_protocol_version(&self) -> FeatureVersion { + 0 + } + + /// returns the type of State Transition + fn state_transition_type(&self) -> StateTransitionType { + Shield + } + + /// Returns IDs of modified data (none for shielded transitions) + fn modified_data_ids(&self) -> Vec { + vec![] + } + + /// State transitions with the same inputs should not be allowed to overlap + fn unique_identifiers(&self) -> Vec { + self.inputs + .iter() + .map(|(key, (nonce, _))| key.base64_string_with_nonce(*nonce)) + .collect() + } + + fn user_fee_increase(&self) -> UserFeeIncrease { + self.user_fee_increase + } + + fn set_user_fee_increase(&mut self, user_fee_increase: UserFeeIncrease) { + self.user_fee_increase = user_fee_increase + } +} + +impl StateTransitionWitnessSigned for ShieldTransitionV0 { + fn inputs( + &self, + ) -> &std::collections::BTreeMap< + crate::address_funds::PlatformAddress, + (crate::prelude::AddressNonce, crate::fee::Credits), + > { + &self.inputs + } + + fn inputs_mut( + &mut self, + ) -> &mut std::collections::BTreeMap< + crate::address_funds::PlatformAddress, + (crate::prelude::AddressNonce, crate::fee::Credits), + > { + &mut self.inputs + } + + fn set_inputs( + &mut self, + inputs: std::collections::BTreeMap< + crate::address_funds::PlatformAddress, + (crate::prelude::AddressNonce, crate::fee::Credits), + >, + ) { + self.inputs = inputs; + } + + fn witnesses(&self) -> &Vec { + &self.input_witnesses + } + + fn set_witnesses(&mut self, witnesses: Vec) { + self.input_witnesses = witnesses; + } +} 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 new file mode 100644 index 00000000000..0aaf4f058b7 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/state_transition_validation.rs @@ -0,0 +1,122 @@ +use crate::consensus::basic::state_transition::{ + FeeStrategyDuplicateError, FeeStrategyEmptyError, FeeStrategyTooManyStepsError, + InputBelowMinimumError, InputWitnessCountMismatchError, ShieldedInvalidValueBalanceError, + TransitionNoInputsError, +}; +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, +}; +use crate::state_transition::StateTransitionStructureValidation; +use crate::validation::SimpleConsensusValidationResult; +use platform_version::version::PlatformVersion; +use std::collections::HashSet; + +impl StateTransitionStructureValidation for ShieldTransitionV0 { + fn validate_structure( + &self, + platform_version: &PlatformVersion, + ) -> SimpleConsensusValidationResult { + // Actions count must be in [1, max] + if let Some(err) = validate_actions_count( + &self.actions, + platform_version + .system_limits + .max_shielded_transition_actions, + ) { + return err; + } + + // Inputs must not be empty (shield requires address funding) + if self.inputs.is_empty() { + return SimpleConsensusValidationResult::new_with_error( + BasicError::TransitionNoInputsError(TransitionNoInputsError::new()).into(), + ); + } + + // Input witnesses must match inputs count + if self.inputs.len() != self.input_witnesses.len() { + return SimpleConsensusValidationResult::new_with_error( + BasicError::InputWitnessCountMismatchError(InputWitnessCountMismatchError::new( + self.inputs.len().min(u16::MAX as usize) as u16, + self.input_witnesses.len().min(u16::MAX as usize) as u16, + )) + .into(), + ); + } + + // Validate each input amount is > 0 + let min_input_amount = platform_version + .dpp + .state_transitions + .address_funds + .min_input_amount; + for (_nonce, amount) in self.inputs.values() { + if *amount < min_input_amount { + return SimpleConsensusValidationResult::new_with_error( + BasicError::InputBelowMinimumError(InputBelowMinimumError::new( + *amount, + min_input_amount, + )) + .into(), + ); + } + } + + // value_balance must be negative (credits flowing into pool) + if self.value_balance >= 0 { + return SimpleConsensusValidationResult::new_with_error( + BasicError::ShieldedInvalidValueBalanceError( + ShieldedInvalidValueBalanceError::new( + "shield value_balance must be negative (credits flow into pool)" + .to_string(), + ), + ) + .into(), + ); + } + + // Proof must not be empty + if let Some(err) = validate_proof_not_empty(&self.proof) { + return err; + } + + // Anchor must not be all zeros + if let Some(err) = validate_anchor_not_zero(&self.anchor) { + return err; + } + + // Fee strategy validation (reuse address funds patterns) + if self.fee_strategy.is_empty() { + return SimpleConsensusValidationResult::new_with_error( + BasicError::FeeStrategyEmptyError(FeeStrategyEmptyError::new()).into(), + ); + } + + let max_fee_strategies = platform_version + .dpp + .state_transitions + .max_address_fee_strategies as usize; + if self.fee_strategy.len() > max_fee_strategies { + return SimpleConsensusValidationResult::new_with_error( + BasicError::FeeStrategyTooManyStepsError(FeeStrategyTooManyStepsError::new( + self.fee_strategy.len().min(u8::MAX as usize) as u8, + max_fee_strategies.min(u8::MAX as usize) as u8, + )) + .into(), + ); + } + + let mut seen = HashSet::with_capacity(self.fee_strategy.len()); + for step in &self.fee_strategy { + if !seen.insert(step) { + return SimpleConsensusValidationResult::new_with_error( + BasicError::FeeStrategyDuplicateError(FeeStrategyDuplicateError::new()).into(), + ); + } + } + + SimpleConsensusValidationResult::new() + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/types.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/types.rs new file mode 100644 index 00000000000..ed94df14858 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/types.rs @@ -0,0 +1,16 @@ +use crate::state_transition::shield_transition::v0::ShieldTransitionV0; +use crate::state_transition::StateTransitionFieldTypes; + +impl StateTransitionFieldTypes for ShieldTransitionV0 { + fn signature_property_paths() -> Vec<&'static str> { + vec![] + } + + fn identifiers_property_paths() -> Vec<&'static str> { + vec![] + } + + fn binary_property_paths() -> Vec<&'static str> { + vec![] + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/v0_methods.rs new file mode 100644 index 00000000000..953d5002a26 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/v0_methods.rs @@ -0,0 +1,66 @@ +#[cfg(feature = "state-transition-signing")] +use std::collections::BTreeMap; + +#[cfg(feature = "state-transition-signing")] +use crate::address_funds::{AddressFundsFeeStrategy, AddressWitness, PlatformAddress}; +#[cfg(feature = "state-transition-signing")] +use crate::fee::Credits; +#[cfg(feature = "state-transition-signing")] +use crate::identity::signer::Signer; +#[cfg(feature = "state-transition-signing")] +use crate::serialization::Signable; +#[cfg(feature = "state-transition-signing")] +use crate::shielded::SerializedAction; +use crate::state_transition::shield_transition::methods::ShieldTransitionMethodsV0; +use crate::state_transition::shield_transition::v0::ShieldTransitionV0; +#[cfg(feature = "state-transition-signing")] +use crate::{ + prelude::{AddressNonce, UserFeeIncrease}, + state_transition::StateTransition, + ProtocolError, +}; +#[cfg(feature = "state-transition-signing")] +use platform_version::version::PlatformVersion; + +impl ShieldTransitionMethodsV0 for ShieldTransitionV0 { + #[cfg(feature = "state-transition-signing")] + fn try_from_bundle_with_signer>( + inputs: BTreeMap, + actions: Vec, + flags: u8, + value_balance: i64, + anchor: [u8; 32], + proof: Vec, + binding_signature: [u8; 64], + fee_strategy: AddressFundsFeeStrategy, + signer: &S, + user_fee_increase: UserFeeIncrease, + _platform_version: &PlatformVersion, + ) -> Result { + // Create the unsigned transition (empty witnesses) + let mut shield_transition = ShieldTransitionV0 { + inputs: inputs.clone(), + actions, + flags, + value_balance, + anchor, + proof, + binding_signature, + fee_strategy, + user_fee_increase, + input_witnesses: Vec::new(), + }; + + // Compute signable bytes (excludes input_witnesses which are marked exclude_from_sig_hash) + let state_transition: StateTransition = shield_transition.clone().into(); + let signable_bytes = state_transition.signable_bytes()?; + + // Sign each input address + shield_transition.input_witnesses = inputs + .keys() + .map(|address| signer.sign_create_witness(address, &signable_bytes)) + .collect::, ProtocolError>>()?; + + Ok(shield_transition.into()) + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/version.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/version.rs new file mode 100644 index 00000000000..27429523509 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/version.rs @@ -0,0 +1,9 @@ +use crate::state_transition::shield_transition::v0::ShieldTransitionV0; +use crate::state_transition::FeatureVersioned; +use crate::version::FeatureVersion; + +impl FeatureVersioned for ShieldTransitionV0 { + fn feature_version(&self) -> FeatureVersion { + 0 + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/version.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/version.rs new file mode 100644 index 00000000000..8f5de0b6b7a --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/version.rs @@ -0,0 +1,11 @@ +use crate::state_transition::shield_transition::ShieldTransition; +use crate::state_transition::FeatureVersioned; +use crate::version::FeatureVersion; + +impl FeatureVersioned for ShieldTransition { + fn feature_version(&self) -> FeatureVersion { + match self { + ShieldTransition::V0(v0) => v0.feature_version(), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/accessors/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/accessors/mod.rs new file mode 100644 index 00000000000..e12d9f4e66f --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/accessors/mod.rs @@ -0,0 +1,14 @@ +mod v0; + +pub use v0::*; + +use crate::shielded::SerializedAction; +use crate::state_transition::shielded_transfer_transition::ShieldedTransferTransition; + +impl ShieldedTransferTransitionAccessorsV0 for ShieldedTransferTransition { + fn actions(&self) -> &[SerializedAction] { + match self { + ShieldedTransferTransition::V0(v0) => &v0.actions, + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/accessors/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/accessors/v0/mod.rs new file mode 100644 index 00000000000..869c6167d28 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/accessors/v0/mod.rs @@ -0,0 +1,15 @@ +use crate::shielded::SerializedAction; + +pub trait ShieldedTransferTransitionAccessorsV0 { + /// Get the serialized Orchard actions + fn actions(&self) -> &[SerializedAction]; + + /// Extract nullifier bytes from each action. + /// Generic over the element type: use `Vec` or `[u8; 32]` as needed. + fn nullifiers>(&self) -> Vec { + self.actions() + .iter() + .map(|a| T::from(a.nullifier)) + .collect() + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/methods/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/methods/mod.rs new file mode 100644 index 00000000000..9e31072690d --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/methods/mod.rs @@ -0,0 +1,51 @@ +mod v0; + +pub use v0::*; + +#[cfg(feature = "state-transition-signing")] +use crate::shielded::SerializedAction; +use crate::state_transition::shielded_transfer_transition::ShieldedTransferTransition; +#[cfg(feature = "state-transition-signing")] +use crate::{ + state_transition::{ + shielded_transfer_transition::v0::ShieldedTransferTransitionV0, StateTransition, + }, + ProtocolError, +}; +#[cfg(feature = "state-transition-signing")] +use platform_version::version::PlatformVersion; + +impl ShieldedTransferTransitionMethodsV0 for ShieldedTransferTransition { + #[cfg(feature = "state-transition-signing")] + fn try_from_bundle( + actions: Vec, + flags: u8, + value_balance: u64, + anchor: [u8; 32], + proof: Vec, + binding_signature: [u8; 64], + platform_version: &PlatformVersion, + ) -> Result { + match platform_version + .dpp + .state_transition_serialization_versions + .shielded_transfer_state_transition + .default_current_version + { + 0 => ShieldedTransferTransitionV0::try_from_bundle( + actions, + flags, + value_balance, + anchor, + proof, + binding_signature, + platform_version, + ), + version => Err(ProtocolError::UnknownVersionMismatch { + method: "ShieldedTransferTransition::try_from_bundle".to_string(), + known_versions: vec![0], + received: version, + }), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/methods/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/methods/v0/mod.rs new file mode 100644 index 00000000000..25a726a6182 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/methods/v0/mod.rs @@ -0,0 +1,25 @@ +#[cfg(feature = "state-transition-signing")] +use crate::shielded::SerializedAction; +use crate::state_transition::StateTransitionType; +#[cfg(feature = "state-transition-signing")] +use crate::{state_transition::StateTransition, ProtocolError}; +#[cfg(feature = "state-transition-signing")] +use platform_version::version::PlatformVersion; + +pub trait ShieldedTransferTransitionMethodsV0 { + #[cfg(feature = "state-transition-signing")] + fn try_from_bundle( + actions: Vec, + flags: u8, + value_balance: u64, + anchor: [u8; 32], + proof: Vec, + binding_signature: [u8; 64], + platform_version: &PlatformVersion, + ) -> Result; + + /// Get State Transition Type + fn get_type() -> StateTransitionType { + StateTransitionType::ShieldedTransfer + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/mod.rs new file mode 100644 index 00000000000..e9a183f249a --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/mod.rs @@ -0,0 +1,64 @@ +pub mod accessors; +pub mod methods; +mod state_transition_estimated_fee_validation; +mod state_transition_like; +mod state_transition_validation; +pub mod v0; +mod version; + +use crate::state_transition::shielded_transfer_transition::v0::ShieldedTransferTransitionV0; +use crate::state_transition::shielded_transfer_transition::v0::ShieldedTransferTransitionV0Signable; +use crate::state_transition::StateTransitionFieldTypes; + +pub type ShieldedTransferTransitionLatest = ShieldedTransferTransitionV0; + +use crate::identity::state_transition::OptionallyAssetLockProved; +use crate::ProtocolError; +use bincode::{Decode, Encode}; +use derive_more::From; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize, PlatformSignable}; +use platform_versioning::PlatformVersioned; +#[cfg(feature = "state-transition-serde-conversion")] +use serde::{Deserialize, Serialize}; + +#[derive( + Debug, + Clone, + Encode, + Decode, + PlatformDeserialize, + PlatformSerialize, + PlatformSignable, + PlatformVersioned, + From, + PartialEq, +)] +#[cfg_attr( + feature = "state-transition-serde-conversion", + derive(Serialize, Deserialize), + serde(tag = "$version") +)] +#[platform_serialize(unversioned)] //versioned directly, no need to use platform_version +#[platform_version_path_bounds( + "dpp.state_transition_serialization_versions.shielded_transfer_state_transition" +)] +pub enum ShieldedTransferTransition { + #[cfg_attr(feature = "state-transition-serde-conversion", serde(rename = "0"))] + V0(ShieldedTransferTransitionV0), +} + +impl OptionallyAssetLockProved for ShieldedTransferTransition {} + +impl StateTransitionFieldTypes for ShieldedTransferTransition { + fn signature_property_paths() -> Vec<&'static str> { + vec![] + } + + fn identifiers_property_paths() -> Vec<&'static str> { + vec![] + } + + fn binary_property_paths() -> Vec<&'static str> { + vec![] + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/state_transition_estimated_fee_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/state_transition_estimated_fee_validation.rs new file mode 100644 index 00000000000..d956aa98757 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/state_transition_estimated_fee_validation.rs @@ -0,0 +1,16 @@ +use crate::fee::Credits; +use crate::state_transition::shielded_transfer_transition::ShieldedTransferTransition; +use crate::state_transition::StateTransitionEstimatedFeeValidation; +use crate::ProtocolError; +use platform_version::version::PlatformVersion; + +impl StateTransitionEstimatedFeeValidation for ShieldedTransferTransition { + fn calculate_min_required_fee( + &self, + _platform_version: &PlatformVersion, + ) -> Result { + // Fee for shielded transfers is paid from value balance in the orchard bundle + // Minimum fee is 0 as the actual fee is extracted from the bundle during validation + Ok(0) + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/state_transition_like.rs new file mode 100644 index 00000000000..a6f2fa9fa74 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/state_transition_like.rs @@ -0,0 +1,41 @@ +use crate::prelude::UserFeeIncrease; +use crate::state_transition::shielded_transfer_transition::ShieldedTransferTransition; +use crate::state_transition::{StateTransitionLike, StateTransitionType}; +use crate::version::FeatureVersion; +use platform_value::Identifier; + +impl StateTransitionLike for ShieldedTransferTransition { + /// Returns ID of the created contract + fn modified_data_ids(&self) -> Vec { + match self { + ShieldedTransferTransition::V0(transition) => transition.modified_data_ids(), + } + } + + fn state_transition_protocol_version(&self) -> FeatureVersion { + match self { + ShieldedTransferTransition::V0(_) => 0, + } + } + /// returns the type of State Transition + fn state_transition_type(&self) -> StateTransitionType { + match self { + ShieldedTransferTransition::V0(transition) => transition.state_transition_type(), + } + } + + /// returns the fee multiplier + fn user_fee_increase(&self) -> UserFeeIncrease { + 0 + } + /// set a fee multiplier + fn set_user_fee_increase(&mut self, _user_fee_increase: UserFeeIncrease) { + // No-op: fee is cryptographically locked by the Orchard binding signature + } + + fn unique_identifiers(&self) -> Vec { + match self { + ShieldedTransferTransition::V0(transition) => transition.unique_identifiers(), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/state_transition_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/state_transition_validation.rs new file mode 100644 index 00000000000..9d3bbbe6ee3 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/state_transition_validation.rs @@ -0,0 +1,15 @@ +use crate::state_transition::shielded_transfer_transition::ShieldedTransferTransition; +use crate::state_transition::StateTransitionStructureValidation; +use crate::validation::SimpleConsensusValidationResult; +use platform_version::version::PlatformVersion; + +impl StateTransitionStructureValidation for ShieldedTransferTransition { + fn validate_structure( + &self, + platform_version: &PlatformVersion, + ) -> SimpleConsensusValidationResult { + match self { + ShieldedTransferTransition::V0(v0) => v0.validate_structure(platform_version), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/mod.rs new file mode 100644 index 00000000000..4d1a846b89c --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/mod.rs @@ -0,0 +1,86 @@ +mod state_transition_like; +mod state_transition_validation; +mod types; +pub(super) mod v0_methods; +mod version; + +use crate::shielded::SerializedAction; +use crate::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize, PlatformSignable}; +#[cfg(feature = "state-transition-serde-conversion")] +use serde::{Deserialize, Serialize}; + +#[derive( + Debug, + Clone, + Encode, + Decode, + PlatformSerialize, + PlatformDeserialize, + PlatformSignable, + PartialEq, +)] +#[cfg_attr( + feature = "state-transition-serde-conversion", + derive(Serialize, Deserialize), + serde(rename_all = "camelCase") +)] +#[platform_serialize(unversioned)] +pub struct ShieldedTransferTransitionV0 { + /// Orchard actions (spend-output pairs) + pub actions: Vec, + /// Bundle flags (spends_enabled | outputs_enabled) + pub flags: u8, + /// Net value balance (fee amount extracted from shielded pool) + pub value_balance: u64, + /// Merkle root of the commitment tree used for spends + pub anchor: [u8; 32], + /// Halo2 proof bytes + pub proof: Vec, + /// RedPallas binding signature + #[cfg_attr( + feature = "state-transition-serde-conversion", + serde(with = "crate::shielded::serde_bytes_64") + )] + pub binding_signature: [u8; 64], +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::serialization::{PlatformDeserializable, PlatformSerializable}; + use std::fmt::Debug; + + fn test_round_trip( + transition: T, + ) where + ::Error: std::fmt::Debug, + { + let serialized = T::serialize_to_bytes(&transition).expect("expected to serialize"); + let deserialized = + T::deserialize_from_bytes(serialized.as_slice()).expect("expected to deserialize"); + assert_eq!(transition, deserialized); + } + + #[test] + fn test_shielded_transfer_transition_v0_serialization_round_trip() { + let transition = ShieldedTransferTransitionV0 { + actions: vec![SerializedAction { + nullifier: [1u8; 32], + rk: [2u8; 32], + cmx: [3u8; 32], + encrypted_note: vec![4u8; 692], + cv_net: [5u8; 32], + spend_auth_sig: [6u8; 64], + }], + flags: 0u8, + value_balance: 0u64, + anchor: [7u8; 32], + proof: vec![8u8; 100], + binding_signature: [9u8; 64], + }; + + test_round_trip(transition); + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/state_transition_like.rs new file mode 100644 index 00000000000..8e6088c3bf5 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/state_transition_like.rs @@ -0,0 +1,51 @@ +use crate::prelude::UserFeeIncrease; +use crate::state_transition::shielded_transfer_transition::v0::ShieldedTransferTransitionV0; +use crate::state_transition::shielded_transfer_transition::ShieldedTransferTransition; +use crate::{ + prelude::Identifier, + state_transition::{StateTransitionLike, StateTransitionType}, +}; + +use crate::state_transition::StateTransition; +use crate::state_transition::StateTransitionType::ShieldedTransfer; +use crate::version::FeatureVersion; + +impl From for StateTransition { + fn from(value: ShieldedTransferTransitionV0) -> Self { + let transition: ShieldedTransferTransition = value.into(); + transition.into() + } +} + +impl StateTransitionLike for ShieldedTransferTransitionV0 { + fn state_transition_protocol_version(&self) -> FeatureVersion { + 0 + } + + /// returns the type of State Transition + fn state_transition_type(&self) -> StateTransitionType { + ShieldedTransfer + } + + /// Returns IDs of modified data (none for shielded transitions) + fn modified_data_ids(&self) -> Vec { + vec![] + } + + /// For ZK-only transitions, uniqueness comes from nullifiers in the actions. + /// Each nullifier can only be used once, making them natural unique identifiers. + fn unique_identifiers(&self) -> Vec { + self.actions + .iter() + .map(|action| hex::encode(action.nullifier)) + .collect() + } + + fn user_fee_increase(&self) -> UserFeeIncrease { + 0 + } + + fn set_user_fee_increase(&mut self, _user_fee_increase: UserFeeIncrease) { + // No-op: fee is cryptographically locked by the Orchard binding signature + } +} 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 new file mode 100644 index 00000000000..39960ef3815 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/state_transition_validation.rs @@ -0,0 +1,50 @@ +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, +}; +use crate::state_transition::StateTransitionStructureValidation; +use crate::validation::SimpleConsensusValidationResult; +use platform_version::version::PlatformVersion; + +impl StateTransitionStructureValidation for ShieldedTransferTransitionV0 { + fn validate_structure( + &self, + platform_version: &PlatformVersion, + ) -> SimpleConsensusValidationResult { + // Actions count must be in [1, max] + if let Some(err) = validate_actions_count( + &self.actions, + platform_version + .system_limits + .max_shielded_transition_actions, + ) { + return err; + } + + // value_balance must fit in i64 (required for Orchard protocol) + if self.value_balance > i64::MAX as u64 { + return SimpleConsensusValidationResult::new_with_error( + BasicError::ShieldedInvalidValueBalanceError( + ShieldedInvalidValueBalanceError::new( + "shielded transfer value_balance exceeds maximum allowed value".to_string(), + ), + ) + .into(), + ); + } + + // Proof must not be empty + if let Some(err) = validate_proof_not_empty(&self.proof) { + return err; + } + + // Anchor must not be all zeros + if let Some(err) = validate_anchor_not_zero(&self.anchor) { + return err; + } + + SimpleConsensusValidationResult::new() + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/types.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/types.rs new file mode 100644 index 00000000000..9c3dc80f9d2 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/types.rs @@ -0,0 +1,16 @@ +use crate::state_transition::shielded_transfer_transition::v0::ShieldedTransferTransitionV0; +use crate::state_transition::StateTransitionFieldTypes; + +impl StateTransitionFieldTypes for ShieldedTransferTransitionV0 { + fn signature_property_paths() -> Vec<&'static str> { + vec![] + } + + fn identifiers_property_paths() -> Vec<&'static str> { + vec![] + } + + fn binary_property_paths() -> Vec<&'static str> { + vec![] + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/v0_methods.rs new file mode 100644 index 00000000000..225d520c81f --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/v0_methods.rs @@ -0,0 +1,31 @@ +#[cfg(feature = "state-transition-signing")] +use crate::shielded::SerializedAction; +use crate::state_transition::shielded_transfer_transition::methods::ShieldedTransferTransitionMethodsV0; +use crate::state_transition::shielded_transfer_transition::v0::ShieldedTransferTransitionV0; +#[cfg(feature = "state-transition-signing")] +use crate::{state_transition::StateTransition, ProtocolError}; +#[cfg(feature = "state-transition-signing")] +use platform_version::version::PlatformVersion; + +impl ShieldedTransferTransitionMethodsV0 for ShieldedTransferTransitionV0 { + #[cfg(feature = "state-transition-signing")] + fn try_from_bundle( + actions: Vec, + flags: u8, + value_balance: u64, + anchor: [u8; 32], + proof: Vec, + binding_signature: [u8; 64], + _platform_version: &PlatformVersion, + ) -> Result { + let transition = ShieldedTransferTransitionV0 { + actions, + flags, + value_balance, + anchor, + proof, + binding_signature, + }; + Ok(transition.into()) + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/version.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/version.rs new file mode 100644 index 00000000000..fb6f180b029 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/version.rs @@ -0,0 +1,9 @@ +use crate::state_transition::shielded_transfer_transition::v0::ShieldedTransferTransitionV0; +use crate::state_transition::FeatureVersioned; +use crate::version::FeatureVersion; + +impl FeatureVersioned for ShieldedTransferTransitionV0 { + fn feature_version(&self) -> FeatureVersion { + 0 + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/version.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/version.rs new file mode 100644 index 00000000000..0688f51e27d --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/version.rs @@ -0,0 +1,11 @@ +use crate::state_transition::shielded_transfer_transition::ShieldedTransferTransition; +use crate::state_transition::FeatureVersioned; +use crate::version::FeatureVersion; + +impl FeatureVersioned for ShieldedTransferTransition { + fn feature_version(&self) -> FeatureVersion { + match self { + ShieldedTransferTransition::V0(v0) => v0.feature_version(), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/accessors/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/accessors/mod.rs new file mode 100644 index 00000000000..d2bdd82cf71 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/accessors/mod.rs @@ -0,0 +1,21 @@ +mod v0; + +pub use v0::*; + +use crate::identity::core_script::CoreScript; +use crate::shielded::SerializedAction; +use crate::state_transition::shielded_withdrawal_transition::ShieldedWithdrawalTransition; + +impl ShieldedWithdrawalTransitionAccessorsV0 for ShieldedWithdrawalTransition { + fn actions(&self) -> &[SerializedAction] { + match self { + ShieldedWithdrawalTransition::V0(v0) => &v0.actions, + } + } + + fn output_script(&self) -> &CoreScript { + match self { + ShieldedWithdrawalTransition::V0(v0) => &v0.output_script, + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/accessors/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/accessors/v0/mod.rs new file mode 100644 index 00000000000..c6fd48b4a1f --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/accessors/v0/mod.rs @@ -0,0 +1,19 @@ +use crate::identity::core_script::CoreScript; +use crate::shielded::SerializedAction; + +pub trait ShieldedWithdrawalTransitionAccessorsV0 { + /// Get the serialized Orchard actions + fn actions(&self) -> &[SerializedAction]; + + /// Get the output script receiving withdrawn funds + fn output_script(&self) -> &CoreScript; + + /// Extract nullifier bytes from each action. + /// Generic over the element type: use `Vec` or `[u8; 32]` as needed. + fn nullifiers>(&self) -> Vec { + self.actions() + .iter() + .map(|a| T::from(a.nullifier)) + .collect() + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/mod.rs new file mode 100644 index 00000000000..ba8da275dbf --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/mod.rs @@ -0,0 +1,63 @@ +mod v0; + +pub use v0::*; + +#[cfg(feature = "state-transition-signing")] +use crate::identity::core_script::CoreScript; +#[cfg(feature = "state-transition-signing")] +use crate::shielded::SerializedAction; +use crate::state_transition::shielded_withdrawal_transition::ShieldedWithdrawalTransition; +#[cfg(feature = "state-transition-signing")] +use crate::withdrawal::Pooling; +#[cfg(feature = "state-transition-signing")] +use crate::{ + state_transition::{ + shielded_withdrawal_transition::v0::ShieldedWithdrawalTransitionV0, StateTransition, + }, + ProtocolError, +}; +#[cfg(feature = "state-transition-signing")] +use platform_version::version::PlatformVersion; + +impl ShieldedWithdrawalTransitionMethodsV0 for ShieldedWithdrawalTransition { + #[cfg(feature = "state-transition-signing")] + fn try_from_bundle( + amount: u64, + actions: Vec, + flags: u8, + value_balance: i64, + anchor: [u8; 32], + proof: Vec, + binding_signature: [u8; 64], + core_fee_per_byte: u32, + pooling: Pooling, + output_script: CoreScript, + platform_version: &PlatformVersion, + ) -> Result { + match platform_version + .dpp + .state_transition_serialization_versions + .shielded_withdrawal_state_transition + .default_current_version + { + 0 => ShieldedWithdrawalTransitionV0::try_from_bundle( + amount, + actions, + flags, + value_balance, + anchor, + proof, + binding_signature, + core_fee_per_byte, + pooling, + output_script, + platform_version, + ), + version => Err(ProtocolError::UnknownVersionMismatch { + method: "ShieldedWithdrawalTransition::try_from_bundle".to_string(), + known_versions: vec![0], + received: version, + }), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/v0/mod.rs new file mode 100644 index 00000000000..53f33aca6f1 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/v0/mod.rs @@ -0,0 +1,34 @@ +#[cfg(feature = "state-transition-signing")] +use crate::identity::core_script::CoreScript; +#[cfg(feature = "state-transition-signing")] +use crate::shielded::SerializedAction; +use crate::state_transition::StateTransitionType; +#[cfg(feature = "state-transition-signing")] +use crate::withdrawal::Pooling; +#[cfg(feature = "state-transition-signing")] +use crate::{state_transition::StateTransition, ProtocolError}; +#[cfg(feature = "state-transition-signing")] +use platform_version::version::PlatformVersion; + +pub trait ShieldedWithdrawalTransitionMethodsV0 { + #[cfg(feature = "state-transition-signing")] + #[allow(clippy::too_many_arguments)] + fn try_from_bundle( + amount: u64, + actions: Vec, + flags: u8, + value_balance: i64, + anchor: [u8; 32], + proof: Vec, + binding_signature: [u8; 64], + core_fee_per_byte: u32, + pooling: Pooling, + output_script: CoreScript, + platform_version: &PlatformVersion, + ) -> Result; + + /// Get State Transition Type + fn get_type() -> StateTransitionType { + StateTransitionType::ShieldedWithdrawal + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/mod.rs new file mode 100644 index 00000000000..9c1209411d6 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/mod.rs @@ -0,0 +1,64 @@ +pub mod accessors; +pub mod methods; +mod state_transition_estimated_fee_validation; +mod state_transition_like; +mod state_transition_validation; +pub mod v0; +mod version; + +use crate::state_transition::shielded_withdrawal_transition::v0::ShieldedWithdrawalTransitionV0; +use crate::state_transition::shielded_withdrawal_transition::v0::ShieldedWithdrawalTransitionV0Signable; +use crate::state_transition::StateTransitionFieldTypes; + +pub type ShieldedWithdrawalTransitionLatest = ShieldedWithdrawalTransitionV0; + +use crate::identity::state_transition::OptionallyAssetLockProved; +use crate::ProtocolError; +use bincode::{Decode, Encode}; +use derive_more::From; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize, PlatformSignable}; +use platform_versioning::PlatformVersioned; +#[cfg(feature = "state-transition-serde-conversion")] +use serde::{Deserialize, Serialize}; + +#[derive( + Debug, + Clone, + Encode, + Decode, + PlatformDeserialize, + PlatformSerialize, + PlatformSignable, + PlatformVersioned, + From, + PartialEq, +)] +#[cfg_attr( + feature = "state-transition-serde-conversion", + derive(Serialize, Deserialize), + serde(tag = "$version") +)] +#[platform_serialize(unversioned)] //versioned directly, no need to use platform_version +#[platform_version_path_bounds( + "dpp.state_transition_serialization_versions.shielded_withdrawal_state_transition" +)] +pub enum ShieldedWithdrawalTransition { + #[cfg_attr(feature = "state-transition-serde-conversion", serde(rename = "0"))] + V0(ShieldedWithdrawalTransitionV0), +} + +impl OptionallyAssetLockProved for ShieldedWithdrawalTransition {} + +impl StateTransitionFieldTypes for ShieldedWithdrawalTransition { + fn signature_property_paths() -> Vec<&'static str> { + vec![] + } + + fn identifiers_property_paths() -> Vec<&'static str> { + vec![] + } + + fn binary_property_paths() -> Vec<&'static str> { + vec![] + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/state_transition_estimated_fee_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/state_transition_estimated_fee_validation.rs new file mode 100644 index 00000000000..43a78d0d4a3 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/state_transition_estimated_fee_validation.rs @@ -0,0 +1,16 @@ +use crate::fee::Credits; +use crate::state_transition::shielded_withdrawal_transition::ShieldedWithdrawalTransition; +use crate::state_transition::StateTransitionEstimatedFeeValidation; +use crate::ProtocolError; +use platform_version::version::PlatformVersion; + +impl StateTransitionEstimatedFeeValidation for ShieldedWithdrawalTransition { + fn calculate_min_required_fee( + &self, + _platform_version: &PlatformVersion, + ) -> Result { + // Fee for shielded withdrawal is paid from value balance in the orchard bundle + // Minimum fee is 0 as the actual fee is extracted from the bundle during validation + Ok(0) + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/state_transition_like.rs new file mode 100644 index 00000000000..fda27be8a1c --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/state_transition_like.rs @@ -0,0 +1,41 @@ +use crate::prelude::UserFeeIncrease; +use crate::state_transition::shielded_withdrawal_transition::ShieldedWithdrawalTransition; +use crate::state_transition::{StateTransitionLike, StateTransitionType}; +use crate::version::FeatureVersion; +use platform_value::Identifier; + +impl StateTransitionLike for ShieldedWithdrawalTransition { + /// Returns ID of the created contract + fn modified_data_ids(&self) -> Vec { + match self { + ShieldedWithdrawalTransition::V0(transition) => transition.modified_data_ids(), + } + } + + fn state_transition_protocol_version(&self) -> FeatureVersion { + match self { + ShieldedWithdrawalTransition::V0(_) => 0, + } + } + /// returns the type of State Transition + fn state_transition_type(&self) -> StateTransitionType { + match self { + ShieldedWithdrawalTransition::V0(transition) => transition.state_transition_type(), + } + } + + /// returns the fee multiplier + fn user_fee_increase(&self) -> UserFeeIncrease { + 0 + } + /// set a fee multiplier + fn set_user_fee_increase(&mut self, _user_fee_increase: UserFeeIncrease) { + // No-op: shielded withdrawal fees are cryptographically locked by the Orchard binding signature + } + + fn unique_identifiers(&self) -> Vec { + match self { + ShieldedWithdrawalTransition::V0(transition) => transition.unique_identifiers(), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/state_transition_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/state_transition_validation.rs new file mode 100644 index 00000000000..c00c5ea9157 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/state_transition_validation.rs @@ -0,0 +1,15 @@ +use crate::state_transition::shielded_withdrawal_transition::ShieldedWithdrawalTransition; +use crate::state_transition::StateTransitionStructureValidation; +use crate::validation::SimpleConsensusValidationResult; +use platform_version::version::PlatformVersion; + +impl StateTransitionStructureValidation for ShieldedWithdrawalTransition { + fn validate_structure( + &self, + platform_version: &PlatformVersion, + ) -> SimpleConsensusValidationResult { + match self { + ShieldedWithdrawalTransition::V0(v0) => v0.validate_structure(platform_version), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs new file mode 100644 index 00000000000..086cad5f132 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs @@ -0,0 +1,100 @@ +mod state_transition_like; +mod state_transition_validation; +mod types; +pub(super) mod v0_methods; +mod version; + +use crate::identity::core_script::CoreScript; +use crate::shielded::SerializedAction; +use crate::withdrawal::Pooling; +use crate::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize, PlatformSignable}; +#[cfg(feature = "state-transition-serde-conversion")] +use serde::{Deserialize, Serialize}; + +#[derive( + Debug, + Clone, + Encode, + Decode, + PlatformSerialize, + PlatformDeserialize, + PlatformSignable, + PartialEq, +)] +#[cfg_attr( + feature = "state-transition-serde-conversion", + derive(Serialize, Deserialize), + serde(rename_all = "camelCase") +)] +#[platform_serialize(unversioned)] +pub struct ShieldedWithdrawalTransitionV0 { + /// Withdrawal amount in credits + pub amount: u64, + /// Orchard actions (spends + change outputs) + pub actions: Vec, + /// Bundle flags (spends_enabled | outputs_enabled) + pub flags: u8, + /// Net value balance (amount + fee flowing out of shielded pool) + pub value_balance: i64, + /// Merkle root of the commitment tree used for spends + pub anchor: [u8; 32], + /// Halo2 proof bytes + pub proof: Vec, + /// RedPallas binding signature + #[cfg_attr( + feature = "state-transition-serde-conversion", + serde(with = "crate::shielded::serde_bytes_64") + )] + pub binding_signature: [u8; 64], + /// Core transaction fee rate + pub core_fee_per_byte: u32, + /// Withdrawal pooling strategy + pub pooling: Pooling, + /// Core address receiving withdrawn funds + pub output_script: CoreScript, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::serialization::{PlatformDeserializable, PlatformSerializable}; + use std::fmt::Debug; + + fn test_round_trip( + transition: T, + ) where + ::Error: std::fmt::Debug, + { + let serialized = T::serialize_to_bytes(&transition).expect("expected to serialize"); + let deserialized = + T::deserialize_from_bytes(serialized.as_slice()).expect("expected to deserialize"); + assert_eq!(transition, deserialized); + } + + #[test] + fn test_shielded_withdrawal_transition_v0_serialization_round_trip() { + let transition = ShieldedWithdrawalTransitionV0 { + amount: 500u64, + actions: vec![SerializedAction { + nullifier: [1u8; 32], + rk: [2u8; 32], + cmx: [3u8; 32], + encrypted_note: vec![4u8; 692], + cv_net: [5u8; 32], + spend_auth_sig: [6u8; 64], + }], + flags: 0u8, + value_balance: 1000i64, + anchor: [7u8; 32], + proof: vec![8u8; 100], + binding_signature: [9u8; 64], + core_fee_per_byte: 1u32, + pooling: Pooling::Never, + output_script: CoreScript::new_p2pkh([11u8; 20]), + }; + + test_round_trip(transition); + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/state_transition_like.rs new file mode 100644 index 00000000000..3f53fe84490 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/state_transition_like.rs @@ -0,0 +1,50 @@ +use crate::state_transition::shielded_withdrawal_transition::v0::ShieldedWithdrawalTransitionV0; +use crate::state_transition::shielded_withdrawal_transition::ShieldedWithdrawalTransition; +use crate::{ + prelude::{Identifier, UserFeeIncrease}, + state_transition::{StateTransitionLike, StateTransitionType}, +}; + +use crate::state_transition::StateTransition; +use crate::state_transition::StateTransitionType::ShieldedWithdrawal; +use crate::version::FeatureVersion; + +impl From for StateTransition { + fn from(value: ShieldedWithdrawalTransitionV0) -> Self { + let transition: ShieldedWithdrawalTransition = value.into(); + transition.into() + } +} + +impl StateTransitionLike for ShieldedWithdrawalTransitionV0 { + fn state_transition_protocol_version(&self) -> FeatureVersion { + 0 + } + + /// returns the type of State Transition + fn state_transition_type(&self) -> StateTransitionType { + ShieldedWithdrawal + } + + /// Returns IDs of modified data (none for shielded transitions) + fn modified_data_ids(&self) -> Vec { + vec![] + } + + /// For ZK-only transitions, uniqueness comes from nullifiers in the actions. + /// Each nullifier can only be used once, making them natural unique identifiers. + fn unique_identifiers(&self) -> Vec { + self.actions + .iter() + .map(|action| hex::encode(action.nullifier)) + .collect() + } + + fn user_fee_increase(&self) -> UserFeeIncrease { + 0 + } + + fn set_user_fee_increase(&mut self, _user_fee_increase: UserFeeIncrease) { + // No-op: shielded withdrawal fees are cryptographically locked by the Orchard binding signature + } +} 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 new file mode 100644 index 00000000000..1b2f68f7afc --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/state_transition_validation.rs @@ -0,0 +1,69 @@ +use crate::consensus::basic::state_transition::{ + ShieldedInvalidValueBalanceError, UnshieldAmountZeroError, UnshieldValueBalanceBelowAmountError, +}; +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, +}; +use crate::state_transition::StateTransitionStructureValidation; +use crate::validation::SimpleConsensusValidationResult; +use platform_version::version::PlatformVersion; + +impl StateTransitionStructureValidation for ShieldedWithdrawalTransitionV0 { + fn validate_structure( + &self, + platform_version: &PlatformVersion, + ) -> SimpleConsensusValidationResult { + // Actions count must be in [1, max] + if let Some(err) = validate_actions_count( + &self.actions, + platform_version + .system_limits + .max_shielded_transition_actions, + ) { + return err; + } + + // Amount must be > 0 + if self.amount == 0 { + return SimpleConsensusValidationResult::new_with_error( + BasicError::UnshieldAmountZeroError(UnshieldAmountZeroError::new()).into(), + ); + } + + // value_balance must be positive (credits flowing out of pool = amount + fee) + if self.value_balance <= 0 { + return SimpleConsensusValidationResult::new_with_error( + BasicError::ShieldedInvalidValueBalanceError( + ShieldedInvalidValueBalanceError::new( + "shielded withdrawal value_balance must be positive".to_string(), + ), + ) + .into(), + ); + } + + // value_balance must be >= amount (value_balance = amount + fee) + if (self.value_balance as u64) < self.amount { + return SimpleConsensusValidationResult::new_with_error( + BasicError::UnshieldValueBalanceBelowAmountError( + UnshieldValueBalanceBelowAmountError::new(self.value_balance, self.amount), + ) + .into(), + ); + } + + // Proof must not be empty + if let Some(err) = validate_proof_not_empty(&self.proof) { + return err; + } + + // Anchor must not be all zeros + if let Some(err) = validate_anchor_not_zero(&self.anchor) { + return err; + } + + SimpleConsensusValidationResult::new() + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/types.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/types.rs new file mode 100644 index 00000000000..5786bdd0554 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/types.rs @@ -0,0 +1,16 @@ +use crate::state_transition::shielded_withdrawal_transition::v0::ShieldedWithdrawalTransitionV0; +use crate::state_transition::StateTransitionFieldTypes; + +impl StateTransitionFieldTypes for ShieldedWithdrawalTransitionV0 { + fn signature_property_paths() -> Vec<&'static str> { + vec![] + } + + fn identifiers_property_paths() -> Vec<&'static str> { + vec![] + } + + fn binary_property_paths() -> Vec<&'static str> { + vec![] + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/v0_methods.rs new file mode 100644 index 00000000000..a3767b3bb73 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/v0_methods.rs @@ -0,0 +1,43 @@ +#[cfg(feature = "state-transition-signing")] +use crate::identity::core_script::CoreScript; +#[cfg(feature = "state-transition-signing")] +use crate::shielded::SerializedAction; +use crate::state_transition::shielded_withdrawal_transition::methods::ShieldedWithdrawalTransitionMethodsV0; +use crate::state_transition::shielded_withdrawal_transition::v0::ShieldedWithdrawalTransitionV0; +#[cfg(feature = "state-transition-signing")] +use crate::withdrawal::Pooling; +#[cfg(feature = "state-transition-signing")] +use crate::{state_transition::StateTransition, ProtocolError}; +#[cfg(feature = "state-transition-signing")] +use platform_version::version::PlatformVersion; + +impl ShieldedWithdrawalTransitionMethodsV0 for ShieldedWithdrawalTransitionV0 { + #[cfg(feature = "state-transition-signing")] + fn try_from_bundle( + amount: u64, + actions: Vec, + flags: u8, + value_balance: i64, + anchor: [u8; 32], + proof: Vec, + binding_signature: [u8; 64], + core_fee_per_byte: u32, + pooling: Pooling, + output_script: CoreScript, + _platform_version: &PlatformVersion, + ) -> Result { + let transition = ShieldedWithdrawalTransitionV0 { + amount, + actions, + flags, + value_balance, + anchor, + proof, + binding_signature, + core_fee_per_byte, + pooling, + output_script, + }; + Ok(transition.into()) + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/version.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/version.rs new file mode 100644 index 00000000000..89d4dfa7750 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/version.rs @@ -0,0 +1,9 @@ +use crate::state_transition::shielded_withdrawal_transition::v0::ShieldedWithdrawalTransitionV0; +use crate::state_transition::FeatureVersioned; +use crate::version::FeatureVersion; + +impl FeatureVersioned for ShieldedWithdrawalTransitionV0 { + fn feature_version(&self) -> FeatureVersion { + 0 + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/version.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/version.rs new file mode 100644 index 00000000000..da339c67331 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/version.rs @@ -0,0 +1,11 @@ +use crate::state_transition::shielded_withdrawal_transition::ShieldedWithdrawalTransition; +use crate::state_transition::FeatureVersioned; +use crate::version::FeatureVersion; + +impl FeatureVersioned for ShieldedWithdrawalTransition { + fn feature_version(&self) -> FeatureVersion { + match self { + ShieldedWithdrawalTransition::V0(v0) => v0.feature_version(), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/accessors/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/accessors/mod.rs new file mode 100644 index 00000000000..2c0619d2159 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/accessors/mod.rs @@ -0,0 +1,21 @@ +mod v0; + +pub use v0::*; + +use crate::address_funds::PlatformAddress; +use crate::shielded::SerializedAction; +use crate::state_transition::unshield_transition::UnshieldTransition; + +impl UnshieldTransitionAccessorsV0 for UnshieldTransition { + fn actions(&self) -> &[SerializedAction] { + match self { + UnshieldTransition::V0(v0) => &v0.actions, + } + } + + fn output_address(&self) -> &PlatformAddress { + match self { + UnshieldTransition::V0(v0) => &v0.output_address, + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/accessors/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/accessors/v0/mod.rs new file mode 100644 index 00000000000..583448b4568 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/accessors/v0/mod.rs @@ -0,0 +1,19 @@ +use crate::address_funds::PlatformAddress; +use crate::shielded::SerializedAction; + +pub trait UnshieldTransitionAccessorsV0 { + /// Get the serialized Orchard actions + fn actions(&self) -> &[SerializedAction]; + + /// Get the output address receiving unshielded funds + fn output_address(&self) -> &PlatformAddress; + + /// Extract nullifier bytes from each action. + /// Generic over the element type: use `Vec` or `[u8; 32]` as needed. + fn nullifiers>(&self) -> Vec { + self.actions() + .iter() + .map(|a| T::from(a.nullifier)) + .collect() + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/mod.rs new file mode 100644 index 00000000000..816dc13c9bd --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/mod.rs @@ -0,0 +1,55 @@ +mod v0; + +pub use v0::*; + +#[cfg(feature = "state-transition-signing")] +use crate::address_funds::PlatformAddress; +#[cfg(feature = "state-transition-signing")] +use crate::shielded::SerializedAction; +use crate::state_transition::unshield_transition::UnshieldTransition; +#[cfg(feature = "state-transition-signing")] +use crate::{ + state_transition::{unshield_transition::v0::UnshieldTransitionV0, StateTransition}, + ProtocolError, +}; +#[cfg(feature = "state-transition-signing")] +use platform_version::version::PlatformVersion; + +impl UnshieldTransitionMethodsV0 for UnshieldTransition { + #[cfg(feature = "state-transition-signing")] + fn try_from_bundle( + output_address: PlatformAddress, + amount: u64, + actions: Vec, + flags: u8, + value_balance: i64, + anchor: [u8; 32], + proof: Vec, + binding_signature: [u8; 64], + platform_version: &PlatformVersion, + ) -> Result { + match platform_version + .dpp + .state_transition_serialization_versions + .unshield_state_transition + .default_current_version + { + 0 => UnshieldTransitionV0::try_from_bundle( + output_address, + amount, + actions, + flags, + value_balance, + anchor, + proof, + binding_signature, + platform_version, + ), + version => Err(ProtocolError::UnknownVersionMismatch { + method: "UnshieldTransition::try_from_bundle".to_string(), + known_versions: vec![0], + received: version, + }), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/v0/mod.rs new file mode 100644 index 00000000000..0842fdd09a4 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/v0/mod.rs @@ -0,0 +1,30 @@ +#[cfg(feature = "state-transition-signing")] +use crate::address_funds::PlatformAddress; +#[cfg(feature = "state-transition-signing")] +use crate::shielded::SerializedAction; +use crate::state_transition::StateTransitionType; +#[cfg(feature = "state-transition-signing")] +use crate::{state_transition::StateTransition, ProtocolError}; +#[cfg(feature = "state-transition-signing")] +use platform_version::version::PlatformVersion; + +pub trait UnshieldTransitionMethodsV0 { + #[cfg(feature = "state-transition-signing")] + #[allow(clippy::too_many_arguments)] + fn try_from_bundle( + output_address: PlatformAddress, + amount: u64, + actions: Vec, + flags: u8, + value_balance: i64, + anchor: [u8; 32], + proof: Vec, + binding_signature: [u8; 64], + platform_version: &PlatformVersion, + ) -> Result; + + /// Get State Transition Type + fn get_type() -> StateTransitionType { + StateTransitionType::Unshield + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/mod.rs new file mode 100644 index 00000000000..410405bb159 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/mod.rs @@ -0,0 +1,64 @@ +pub mod accessors; +pub mod methods; +mod state_transition_estimated_fee_validation; +mod state_transition_like; +mod state_transition_validation; +pub mod v0; +mod version; + +use crate::state_transition::unshield_transition::v0::UnshieldTransitionV0; +use crate::state_transition::unshield_transition::v0::UnshieldTransitionV0Signable; +use crate::state_transition::StateTransitionFieldTypes; + +pub type UnshieldTransitionLatest = UnshieldTransitionV0; + +use crate::identity::state_transition::OptionallyAssetLockProved; +use crate::ProtocolError; +use bincode::{Decode, Encode}; +use derive_more::From; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize, PlatformSignable}; +use platform_versioning::PlatformVersioned; +#[cfg(feature = "state-transition-serde-conversion")] +use serde::{Deserialize, Serialize}; + +#[derive( + Debug, + Clone, + Encode, + Decode, + PlatformDeserialize, + PlatformSerialize, + PlatformSignable, + PlatformVersioned, + From, + PartialEq, +)] +#[cfg_attr( + feature = "state-transition-serde-conversion", + derive(Serialize, Deserialize), + serde(tag = "$version") +)] +#[platform_serialize(unversioned)] //versioned directly, no need to use platform_version +#[platform_version_path_bounds( + "dpp.state_transition_serialization_versions.unshield_state_transition" +)] +pub enum UnshieldTransition { + #[cfg_attr(feature = "state-transition-serde-conversion", serde(rename = "0"))] + V0(UnshieldTransitionV0), +} + +impl OptionallyAssetLockProved for UnshieldTransition {} + +impl StateTransitionFieldTypes for UnshieldTransition { + fn signature_property_paths() -> Vec<&'static str> { + vec![] + } + + fn identifiers_property_paths() -> Vec<&'static str> { + vec![] + } + + fn binary_property_paths() -> Vec<&'static str> { + vec![] + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/state_transition_estimated_fee_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/state_transition_estimated_fee_validation.rs new file mode 100644 index 00000000000..4f1d94063e7 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/state_transition_estimated_fee_validation.rs @@ -0,0 +1,16 @@ +use crate::fee::Credits; +use crate::state_transition::unshield_transition::UnshieldTransition; +use crate::state_transition::StateTransitionEstimatedFeeValidation; +use crate::ProtocolError; +use platform_version::version::PlatformVersion; + +impl StateTransitionEstimatedFeeValidation for UnshieldTransition { + fn calculate_min_required_fee( + &self, + _platform_version: &PlatformVersion, + ) -> Result { + // Fee for unshield is paid from value balance in the orchard bundle + // Minimum fee is 0 as the actual fee is extracted from the bundle during validation + Ok(0) + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/state_transition_like.rs new file mode 100644 index 00000000000..658cd5aeb7a --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/state_transition_like.rs @@ -0,0 +1,41 @@ +use crate::prelude::UserFeeIncrease; +use crate::state_transition::unshield_transition::UnshieldTransition; +use crate::state_transition::{StateTransitionLike, StateTransitionType}; +use crate::version::FeatureVersion; +use platform_value::Identifier; + +impl StateTransitionLike for UnshieldTransition { + /// Returns ID of the created contract + fn modified_data_ids(&self) -> Vec { + match self { + UnshieldTransition::V0(transition) => transition.modified_data_ids(), + } + } + + fn state_transition_protocol_version(&self) -> FeatureVersion { + match self { + UnshieldTransition::V0(_) => 0, + } + } + /// returns the type of State Transition + fn state_transition_type(&self) -> StateTransitionType { + match self { + UnshieldTransition::V0(transition) => transition.state_transition_type(), + } + } + + /// returns the fee multiplier + fn user_fee_increase(&self) -> UserFeeIncrease { + 0 + } + /// set a fee multiplier + fn set_user_fee_increase(&mut self, _user_fee_increase: UserFeeIncrease) { + // No-op: fee is cryptographically locked by the Orchard binding signature + } + + fn unique_identifiers(&self) -> Vec { + match self { + UnshieldTransition::V0(transition) => transition.unique_identifiers(), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/state_transition_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/state_transition_validation.rs new file mode 100644 index 00000000000..bfc5be90735 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/state_transition_validation.rs @@ -0,0 +1,15 @@ +use crate::state_transition::unshield_transition::UnshieldTransition; +use crate::state_transition::StateTransitionStructureValidation; +use crate::validation::SimpleConsensusValidationResult; +use platform_version::version::PlatformVersion; + +impl StateTransitionStructureValidation for UnshieldTransition { + fn validate_structure( + &self, + platform_version: &PlatformVersion, + ) -> SimpleConsensusValidationResult { + match self { + UnshieldTransition::V0(v0) => v0.validate_structure(platform_version), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs new file mode 100644 index 00000000000..989643bf2f6 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs @@ -0,0 +1,96 @@ +mod state_transition_like; +mod state_transition_validation; +mod types; +pub(super) mod v0_methods; +mod version; + +use crate::address_funds::PlatformAddress; +use crate::shielded::SerializedAction; +use crate::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize, PlatformSignable}; +#[cfg(feature = "state-transition-serde-conversion")] +use serde::{Deserialize, Serialize}; + +#[derive( + Debug, + Clone, + Encode, + Decode, + PlatformSerialize, + PlatformDeserialize, + PlatformSignable, + PartialEq, +)] +#[cfg_attr( + feature = "state-transition-serde-conversion", + derive(Serialize, Deserialize), + serde(rename_all = "camelCase") +)] +#[platform_serialize(unversioned)] +pub struct UnshieldTransitionV0 { + /// Address receiving the unshielded funds + pub output_address: PlatformAddress, + /// Amount being unshielded (in credits) + pub amount: u64, + /// Orchard actions (spend-output pairs) + pub actions: Vec, + /// Bundle flags (spends_enabled | outputs_enabled) + pub flags: u8, + /// Net value balance (amount + fee flowing out of shielded pool) + pub value_balance: i64, + /// Merkle root of the commitment tree used for spends + pub anchor: [u8; 32], + /// Halo2 proof bytes + pub proof: Vec, + /// RedPallas binding signature + #[cfg_attr( + feature = "state-transition-serde-conversion", + serde(with = "crate::shielded::serde_bytes_64") + )] + pub binding_signature: [u8; 64], +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::address_funds::PlatformAddress; + use crate::serialization::{PlatformDeserializable, PlatformSerializable}; + use std::fmt::Debug; + + fn test_round_trip( + transition: T, + ) where + ::Error: std::fmt::Debug, + { + let serialized = T::serialize_to_bytes(&transition).expect("expected to serialize"); + let deserialized = + T::deserialize_from_bytes(serialized.as_slice()).expect("expected to deserialize"); + assert_eq!(transition, deserialized); + } + + #[test] + fn test_unshield_transition_v0_serialization_round_trip() { + let transition = UnshieldTransitionV0 { + output_address: PlatformAddress::P2pkh([ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + ]), + amount: 500u64, + actions: vec![SerializedAction { + nullifier: [1u8; 32], + rk: [2u8; 32], + cmx: [3u8; 32], + encrypted_note: vec![4u8; 692], + cv_net: [5u8; 32], + spend_auth_sig: [6u8; 64], + }], + flags: 0u8, + value_balance: 1000i64, + anchor: [7u8; 32], + proof: vec![8u8; 100], + binding_signature: [9u8; 64], + }; + + test_round_trip(transition); + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/state_transition_like.rs new file mode 100644 index 00000000000..a4120d242bb --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/state_transition_like.rs @@ -0,0 +1,50 @@ +use crate::state_transition::unshield_transition::v0::UnshieldTransitionV0; +use crate::state_transition::unshield_transition::UnshieldTransition; +use crate::{ + prelude::{Identifier, UserFeeIncrease}, + state_transition::{StateTransitionLike, StateTransitionType}, +}; + +use crate::state_transition::StateTransition; +use crate::state_transition::StateTransitionType::Unshield; +use crate::version::FeatureVersion; + +impl From for StateTransition { + fn from(value: UnshieldTransitionV0) -> Self { + let transition: UnshieldTransition = value.into(); + transition.into() + } +} + +impl StateTransitionLike for UnshieldTransitionV0 { + fn state_transition_protocol_version(&self) -> FeatureVersion { + 0 + } + + /// returns the type of State Transition + fn state_transition_type(&self) -> StateTransitionType { + Unshield + } + + /// Returns IDs of modified data (none for shielded transitions) + fn modified_data_ids(&self) -> Vec { + vec![] + } + + /// For ZK-only transitions, uniqueness comes from nullifiers in the actions. + /// Each nullifier can only be used once, making them natural unique identifiers. + fn unique_identifiers(&self) -> Vec { + self.actions + .iter() + .map(|action| hex::encode(action.nullifier)) + .collect() + } + + fn user_fee_increase(&self) -> UserFeeIncrease { + 0 + } + + fn set_user_fee_increase(&mut self, _user_fee_increase: UserFeeIncrease) { + // No-op: fee is cryptographically locked by the Orchard binding signature + } +} 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 new file mode 100644 index 00000000000..f59ccccc948 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/state_transition_validation.rs @@ -0,0 +1,69 @@ +use crate::consensus::basic::state_transition::{ + ShieldedInvalidValueBalanceError, UnshieldAmountZeroError, UnshieldValueBalanceBelowAmountError, +}; +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, +}; +use crate::state_transition::unshield_transition::v0::UnshieldTransitionV0; +use crate::state_transition::StateTransitionStructureValidation; +use crate::validation::SimpleConsensusValidationResult; +use platform_version::version::PlatformVersion; + +impl StateTransitionStructureValidation for UnshieldTransitionV0 { + fn validate_structure( + &self, + platform_version: &PlatformVersion, + ) -> SimpleConsensusValidationResult { + // Actions count must be in [1, max] + if let Some(err) = validate_actions_count( + &self.actions, + platform_version + .system_limits + .max_shielded_transition_actions, + ) { + return err; + } + + // Amount must be > 0 + if self.amount == 0 { + return SimpleConsensusValidationResult::new_with_error( + BasicError::UnshieldAmountZeroError(UnshieldAmountZeroError::new()).into(), + ); + } + + // value_balance must be positive (credits flowing out of pool = amount + fee) + if self.value_balance <= 0 { + return SimpleConsensusValidationResult::new_with_error( + BasicError::ShieldedInvalidValueBalanceError( + ShieldedInvalidValueBalanceError::new( + "unshield value_balance must be positive".to_string(), + ), + ) + .into(), + ); + } + + // value_balance must be >= amount (value_balance = amount + fee) + if (self.value_balance as u64) < self.amount { + return SimpleConsensusValidationResult::new_with_error( + BasicError::UnshieldValueBalanceBelowAmountError( + UnshieldValueBalanceBelowAmountError::new(self.value_balance, self.amount), + ) + .into(), + ); + } + + // Proof must not be empty + if let Some(err) = validate_proof_not_empty(&self.proof) { + return err; + } + + // Anchor must not be all zeros + if let Some(err) = validate_anchor_not_zero(&self.anchor) { + return err; + } + + SimpleConsensusValidationResult::new() + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/types.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/types.rs new file mode 100644 index 00000000000..928054d5a4f --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/types.rs @@ -0,0 +1,16 @@ +use crate::state_transition::unshield_transition::v0::UnshieldTransitionV0; +use crate::state_transition::StateTransitionFieldTypes; + +impl StateTransitionFieldTypes for UnshieldTransitionV0 { + fn signature_property_paths() -> Vec<&'static str> { + vec![] + } + + fn identifiers_property_paths() -> Vec<&'static str> { + vec![] + } + + fn binary_property_paths() -> Vec<&'static str> { + vec![] + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/v0_methods.rs new file mode 100644 index 00000000000..e8b1714de4c --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/v0_methods.rs @@ -0,0 +1,37 @@ +#[cfg(feature = "state-transition-signing")] +use crate::address_funds::PlatformAddress; +#[cfg(feature = "state-transition-signing")] +use crate::shielded::SerializedAction; +use crate::state_transition::unshield_transition::methods::UnshieldTransitionMethodsV0; +use crate::state_transition::unshield_transition::v0::UnshieldTransitionV0; +#[cfg(feature = "state-transition-signing")] +use crate::{state_transition::StateTransition, ProtocolError}; +#[cfg(feature = "state-transition-signing")] +use platform_version::version::PlatformVersion; + +impl UnshieldTransitionMethodsV0 for UnshieldTransitionV0 { + #[cfg(feature = "state-transition-signing")] + fn try_from_bundle( + output_address: PlatformAddress, + amount: u64, + actions: Vec, + flags: u8, + value_balance: i64, + anchor: [u8; 32], + proof: Vec, + binding_signature: [u8; 64], + _platform_version: &PlatformVersion, + ) -> Result { + let transition = UnshieldTransitionV0 { + output_address, + amount, + actions, + flags, + value_balance, + anchor, + proof, + binding_signature, + }; + Ok(transition.into()) + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/version.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/version.rs new file mode 100644 index 00000000000..540c8bc9e45 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/version.rs @@ -0,0 +1,9 @@ +use crate::state_transition::unshield_transition::v0::UnshieldTransitionV0; +use crate::state_transition::FeatureVersioned; +use crate::version::FeatureVersion; + +impl FeatureVersioned for UnshieldTransitionV0 { + fn feature_version(&self) -> FeatureVersion { + 0 + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/version.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/version.rs new file mode 100644 index 00000000000..83c92c77caa --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/version.rs @@ -0,0 +1,11 @@ +use crate::state_transition::unshield_transition::UnshieldTransition; +use crate::state_transition::FeatureVersioned; +use crate::version::FeatureVersion; + +impl FeatureVersioned for UnshieldTransition { + fn feature_version(&self) -> FeatureVersion { + match self { + UnshieldTransition::V0(v0) => v0.feature_version(), + } + } +} diff --git a/packages/rs-platform-version/Cargo.toml b/packages/rs-platform-version/Cargo.toml index f7b7e5d2e8b..3593be0ef15 100644 --- a/packages/rs-platform-version/Cargo.toml +++ b/packages/rs-platform-version/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" thiserror = { version = "2.0.12" } bincode = { version = "=2.0.1" } versioned-feature-core = { git = "https://github.com/dashpay/versioned-feature-core", version = "1.0.0" } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "33dfd48a1718160cb333fa95424be491785f1897" } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "7ecb8465fad750c7cddd5332adb6f97fcceb498b" } [features] mock-versions = [] diff --git a/packages/rs-platform-version/src/version/dpp_versions/dpp_state_transition_serialization_versions/mod.rs b/packages/rs-platform-version/src/version/dpp_versions/dpp_state_transition_serialization_versions/mod.rs index b1a027dc430..5b6a458dc38 100644 --- a/packages/rs-platform-version/src/version/dpp_versions/dpp_state_transition_serialization_versions/mod.rs +++ b/packages/rs-platform-version/src/version/dpp_versions/dpp_state_transition_serialization_versions/mod.rs @@ -28,6 +28,11 @@ pub struct DPPStateTransitionSerializationVersions { pub address_funds_transfer_state_transition: FeatureVersionBounds, pub address_funding_from_asset_lock_state_transition: FeatureVersionBounds, pub address_credit_withdrawal_state_transition: FeatureVersionBounds, + pub shield_state_transition: FeatureVersionBounds, + pub shielded_transfer_state_transition: FeatureVersionBounds, + pub unshield_state_transition: FeatureVersionBounds, + pub shield_from_asset_lock_state_transition: FeatureVersionBounds, + pub shielded_withdrawal_state_transition: FeatureVersionBounds, } #[derive(Clone, Debug, Default)] diff --git a/packages/rs-platform-version/src/version/dpp_versions/dpp_state_transition_serialization_versions/v1.rs b/packages/rs-platform-version/src/version/dpp_versions/dpp_state_transition_serialization_versions/v1.rs index 88cd1d3c661..478f87dee89 100644 --- a/packages/rs-platform-version/src/version/dpp_versions/dpp_state_transition_serialization_versions/v1.rs +++ b/packages/rs-platform-version/src/version/dpp_versions/dpp_state_transition_serialization_versions/v1.rs @@ -132,4 +132,29 @@ pub const STATE_TRANSITION_SERIALIZATION_VERSIONS_V1: DPPStateTransitionSerializ max_version: 0, default_current_version: 0, }, + shield_state_transition: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + shielded_transfer_state_transition: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + unshield_state_transition: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + shield_from_asset_lock_state_transition: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + shielded_withdrawal_state_transition: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, }; diff --git a/packages/rs-platform-version/src/version/dpp_versions/dpp_state_transition_serialization_versions/v2.rs b/packages/rs-platform-version/src/version/dpp_versions/dpp_state_transition_serialization_versions/v2.rs index b3b5292c76d..b2b12cae1af 100644 --- a/packages/rs-platform-version/src/version/dpp_versions/dpp_state_transition_serialization_versions/v2.rs +++ b/packages/rs-platform-version/src/version/dpp_versions/dpp_state_transition_serialization_versions/v2.rs @@ -132,4 +132,29 @@ pub const STATE_TRANSITION_SERIALIZATION_VERSIONS_V2: DPPStateTransitionSerializ max_version: 0, default_current_version: 0, }, + shield_state_transition: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + shielded_transfer_state_transition: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + unshield_state_transition: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + shield_from_asset_lock_state_transition: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + shielded_withdrawal_state_transition: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, }; diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/mod.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/mod.rs index ea5c2dc17e7..e6b418f8d44 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/mod.rs @@ -134,6 +134,7 @@ pub struct DriveAbciBlockEndMethodVersions { pub validator_set_update: FeatureVersion, pub should_checkpoint: OptionalFeatureVersion, pub update_checkpoints: OptionalFeatureVersion, + pub record_shielded_pool_anchor: OptionalFeatureVersion, } #[derive(Clone, Debug, Default)] @@ -177,4 +178,6 @@ pub struct DriveAbciStateTransitionProcessingMethodVersions { pub validate_fees_of_event: FeatureVersion, pub store_address_balances_to_recent_block_storage: OptionalFeatureVersion, pub cleanup_recent_block_storage_address_balances: OptionalFeatureVersion, + pub store_nullifiers_to_recent_block_storage: OptionalFeatureVersion, + pub cleanup_recent_block_storage_nullifiers: OptionalFeatureVersion, } diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v1.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v1.rs index 652a29ca66f..b3d426cc8ff 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v1.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v1.rs @@ -107,6 +107,8 @@ pub const DRIVE_ABCI_METHOD_VERSIONS_V1: DriveAbciMethodVersions = DriveAbciMeth validate_fees_of_event: 0, store_address_balances_to_recent_block_storage: None, cleanup_recent_block_storage_address_balances: None, + store_nullifiers_to_recent_block_storage: None, + cleanup_recent_block_storage_nullifiers: None, }, epoch: DriveAbciEpochMethodVersions { gather_epoch_info: 0, @@ -121,6 +123,7 @@ pub const DRIVE_ABCI_METHOD_VERSIONS_V1: DriveAbciMethodVersions = DriveAbciMeth validator_set_update: 0, should_checkpoint: None, update_checkpoints: None, + record_shielded_pool_anchor: None, }, platform_state_storage: DriveAbciPlatformStateStorageMethodVersions { fetch_platform_state: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v2.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v2.rs index ca20542a937..077f5a7eb4e 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v2.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v2.rs @@ -108,6 +108,8 @@ pub const DRIVE_ABCI_METHOD_VERSIONS_V2: DriveAbciMethodVersions = DriveAbciMeth validate_fees_of_event: 0, store_address_balances_to_recent_block_storage: None, cleanup_recent_block_storage_address_balances: None, + store_nullifiers_to_recent_block_storage: None, + cleanup_recent_block_storage_nullifiers: None, }, epoch: DriveAbciEpochMethodVersions { gather_epoch_info: 0, @@ -122,6 +124,7 @@ pub const DRIVE_ABCI_METHOD_VERSIONS_V2: DriveAbciMethodVersions = DriveAbciMeth validator_set_update: 0, should_checkpoint: None, update_checkpoints: None, + record_shielded_pool_anchor: None, }, platform_state_storage: DriveAbciPlatformStateStorageMethodVersions { fetch_platform_state: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v3.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v3.rs index 40d6b8d3a17..74f40dfe23c 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v3.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v3.rs @@ -107,6 +107,8 @@ pub const DRIVE_ABCI_METHOD_VERSIONS_V3: DriveAbciMethodVersions = DriveAbciMeth validate_fees_of_event: 0, store_address_balances_to_recent_block_storage: None, cleanup_recent_block_storage_address_balances: None, + store_nullifiers_to_recent_block_storage: None, + cleanup_recent_block_storage_nullifiers: None, }, epoch: DriveAbciEpochMethodVersions { gather_epoch_info: 0, @@ -121,6 +123,7 @@ pub const DRIVE_ABCI_METHOD_VERSIONS_V3: DriveAbciMethodVersions = DriveAbciMeth validator_set_update: 1, should_checkpoint: None, update_checkpoints: None, + record_shielded_pool_anchor: None, }, platform_state_storage: DriveAbciPlatformStateStorageMethodVersions { fetch_platform_state: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v4.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v4.rs index 87e70731216..ae7233761b9 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v4.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v4.rs @@ -107,6 +107,8 @@ pub const DRIVE_ABCI_METHOD_VERSIONS_V4: DriveAbciMethodVersions = DriveAbciMeth validate_fees_of_event: 0, store_address_balances_to_recent_block_storage: None, cleanup_recent_block_storage_address_balances: None, + store_nullifiers_to_recent_block_storage: None, + cleanup_recent_block_storage_nullifiers: None, }, epoch: DriveAbciEpochMethodVersions { gather_epoch_info: 0, @@ -121,6 +123,7 @@ pub const DRIVE_ABCI_METHOD_VERSIONS_V4: DriveAbciMethodVersions = DriveAbciMeth validator_set_update: 2, should_checkpoint: None, update_checkpoints: None, + record_shielded_pool_anchor: None, }, platform_state_storage: DriveAbciPlatformStateStorageMethodVersions { fetch_platform_state: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v5.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v5.rs index faf90d1b788..ea35cd1fefb 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v5.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v5.rs @@ -111,6 +111,8 @@ pub const DRIVE_ABCI_METHOD_VERSIONS_V5: DriveAbciMethodVersions = DriveAbciMeth validate_fees_of_event: 0, store_address_balances_to_recent_block_storage: None, cleanup_recent_block_storage_address_balances: None, + store_nullifiers_to_recent_block_storage: None, + cleanup_recent_block_storage_nullifiers: None, }, epoch: DriveAbciEpochMethodVersions { gather_epoch_info: 0, @@ -125,6 +127,7 @@ pub const DRIVE_ABCI_METHOD_VERSIONS_V5: DriveAbciMethodVersions = DriveAbciMeth validator_set_update: 2, should_checkpoint: None, update_checkpoints: None, + record_shielded_pool_anchor: None, }, platform_state_storage: DriveAbciPlatformStateStorageMethodVersions { fetch_platform_state: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v6.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v6.rs index ed161b042c7..0e8a9e55068 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v6.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v6.rs @@ -109,6 +109,8 @@ pub const DRIVE_ABCI_METHOD_VERSIONS_V6: DriveAbciMethodVersions = DriveAbciMeth validate_fees_of_event: 0, store_address_balances_to_recent_block_storage: None, cleanup_recent_block_storage_address_balances: None, + store_nullifiers_to_recent_block_storage: None, + cleanup_recent_block_storage_nullifiers: None, }, epoch: DriveAbciEpochMethodVersions { gather_epoch_info: 0, @@ -123,6 +125,7 @@ pub const DRIVE_ABCI_METHOD_VERSIONS_V6: DriveAbciMethodVersions = DriveAbciMeth validator_set_update: 2, should_checkpoint: None, update_checkpoints: None, + record_shielded_pool_anchor: None, }, platform_state_storage: DriveAbciPlatformStateStorageMethodVersions { fetch_platform_state: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v7.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v7.rs index 718555743ab..72ce4a00251 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v7.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v7.rs @@ -108,6 +108,8 @@ pub const DRIVE_ABCI_METHOD_VERSIONS_V7: DriveAbciMethodVersions = DriveAbciMeth validate_fees_of_event: 0, store_address_balances_to_recent_block_storage: Some(0), // changed cleanup_recent_block_storage_address_balances: Some(0), // cleanup enabled when store is enabled + store_nullifiers_to_recent_block_storage: Some(0), + cleanup_recent_block_storage_nullifiers: Some(0), }, epoch: DriveAbciEpochMethodVersions { gather_epoch_info: 0, @@ -122,6 +124,7 @@ pub const DRIVE_ABCI_METHOD_VERSIONS_V7: DriveAbciMethodVersions = DriveAbciMeth validator_set_update: 2, should_checkpoint: Some(0), update_checkpoints: Some(0), + record_shielded_pool_anchor: Some(0), }, platform_state_storage: DriveAbciPlatformStateStorageMethodVersions { fetch_platform_state: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/mod.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/mod.rs index df96bd63440..20b93183e74 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/mod.rs @@ -17,6 +17,7 @@ pub struct DriveAbciQueryVersions { pub system: DriveAbciQuerySystemVersions, pub group_queries: DriveAbciQueryGroupVersions, pub address_funds_queries: DriveAbciQueryAddressFundsVersions, + pub shielded_queries: DriveAbciQueryShieldedVersions, } #[derive(Clone, Debug, Default)] @@ -92,6 +93,21 @@ pub struct DriveAbciQueryDataContractVersions { pub data_contracts: FeatureVersionBounds, } +#[derive(Clone, Debug, Default)] +pub struct DriveAbciQueryShieldedVersions { + pub encrypted_notes: FeatureVersionBounds, + pub anchors: FeatureVersionBounds, + pub pool_state: FeatureVersionBounds, + pub nullifiers: FeatureVersionBounds, + pub nullifiers_trunk_state: FeatureVersionBounds, + pub nullifiers_branch_state: FeatureVersionBounds, + pub recent_nullifier_changes: FeatureVersionBounds, + pub recent_compacted_nullifier_changes: FeatureVersionBounds, + /// Maximum number of encrypted notes returned per query. + /// Should match the BulkAppendTree buffer capacity (2^chunk_power). + pub max_encrypted_notes_per_query: u16, +} + #[derive(Clone, Debug, Default)] pub struct DriveAbciQuerySystemVersions { pub version_upgrade_state: FeatureVersionBounds, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/v1.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/v1.rs index 9a9652733fa..3554ff647be 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/v1.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/v1.rs @@ -1,9 +1,9 @@ use crate::version::drive_abci_versions::drive_abci_query_versions::{ DriveAbciQueryAddressFundsVersions, DriveAbciQueryDataContractVersions, DriveAbciQueryGroupVersions, DriveAbciQueryIdentityVersions, - DriveAbciQueryPrefundedSpecializedBalancesVersions, DriveAbciQuerySystemVersions, - DriveAbciQueryTokenVersions, DriveAbciQueryValidatorVersions, DriveAbciQueryVersions, - DriveAbciQueryVotingVersions, + DriveAbciQueryPrefundedSpecializedBalancesVersions, DriveAbciQueryShieldedVersions, + DriveAbciQuerySystemVersions, DriveAbciQueryTokenVersions, DriveAbciQueryValidatorVersions, + DriveAbciQueryVersions, DriveAbciQueryVotingVersions, }; use versioned_feature_core::FeatureVersionBounds; @@ -247,6 +247,49 @@ pub const DRIVE_ABCI_QUERY_VERSIONS_V1: DriveAbciQueryVersions = DriveAbciQueryV default_current_version: 0, }, }, + shielded_queries: DriveAbciQueryShieldedVersions { + encrypted_notes: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + anchors: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + pool_state: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + nullifiers: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + nullifiers_trunk_state: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + nullifiers_branch_state: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + recent_nullifier_changes: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + recent_compacted_nullifier_changes: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + max_encrypted_notes_per_query: 2048, + }, address_funds_queries: DriveAbciQueryAddressFundsVersions { addresses_infos: FeatureVersionBounds { min_version: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/mod.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/mod.rs index fc8364f8201..24e6f1cf8cc 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/mod.rs @@ -15,6 +15,8 @@ pub struct DriveAbciValidationVersions { pub has_nonce_validation: FeatureVersion, pub has_address_witness_validation: FeatureVersion, pub validate_address_witnesses: FeatureVersion, + pub validate_shielded_proof: FeatureVersion, + pub validate_minimum_shielded_fee: FeatureVersion, pub process_state_transition: FeatureVersion, pub state_transition_to_execution_event_for_check_tx: FeatureVersion, pub penalties: PenaltyAmounts, @@ -25,6 +27,16 @@ pub struct DriveAbciValidationVersions { pub struct DriveAbciValidationConstants { pub maximum_vote_polls_to_process: u16, pub maximum_contenders_to_consider: u16, + /// Minimum number of encrypted notes in the shielded pool before outgoing + /// transitions (Unshield, ShieldedWithdrawal) are allowed. This ensures a + /// sufficient anonymity set before funds can leave the pool. + pub minimum_pool_notes_for_outgoing: u64, + /// Per-bundle fee (in credits) for Halo 2 ZK proof verification. + /// Benchmarked at ~30x per-action signature verification cost. + pub shielded_proof_verification_fee: u64, + /// Per-action fee (in credits) for processing: RedPallas spend auth signature + /// verification, nullifier duplicate check, and tree insertion. + pub shielded_per_action_processing_fee: u64, } #[derive(Clone, Debug, Default)] @@ -60,6 +72,12 @@ pub struct DriveAbciStateTransitionValidationVersions { pub address_credit_withdrawal: DriveAbciStateTransitionValidationVersion, pub address_funds_from_asset_lock: DriveAbciStateTransitionValidationVersion, pub address_funds_transfer: DriveAbciStateTransitionValidationVersion, + + pub shield_state_transition: DriveAbciStateTransitionValidationVersion, + pub shielded_transfer_state_transition: DriveAbciStateTransitionValidationVersion, + pub unshield_state_transition: DriveAbciStateTransitionValidationVersion, + pub shield_from_asset_lock_state_transition: DriveAbciStateTransitionValidationVersion, + pub shielded_withdrawal_state_transition: DriveAbciStateTransitionValidationVersion, } #[derive(Clone, Debug, Default)] @@ -84,6 +102,8 @@ pub struct PenaltyAmounts { pub validation_of_added_keys_proof_of_possession_failure: u64, /// Penalty for address funding with insufficient funds for outputs pub address_funds_insufficient_balance: u64, + /// Penalty for submitting a shield transition with an invalid ZK proof + pub shielded_proof_verification_failure: u64, } #[derive(Clone, Copy, Debug, Default)] diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v1.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v1.rs index 5fe30a0fb84..45dce6261b5 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v1.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v1.rs @@ -200,10 +200,52 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V1: DriveAbciValidationVersions = state: 0, transform_into_action: 0, }, + shield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_transfer_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + unshield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shield_from_asset_lock_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_withdrawal_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, }, has_nonce_validation: 0, has_address_witness_validation: 0, validate_address_witnesses: 0, + validate_shielded_proof: 0, + validate_minimum_shielded_fee: 0, process_state_transition: 0, state_transition_to_execution_event_for_check_tx: 0, penalties: PenaltyAmounts { @@ -212,9 +254,13 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V1: DriveAbciValidationVersions = validation_of_added_keys_structure_failure: 10000000, validation_of_added_keys_proof_of_possession_failure: 50000000, address_funds_insufficient_balance: 10000000, + shielded_proof_verification_failure: 50000000, }, event_constants: DriveAbciValidationConstants { maximum_vote_polls_to_process: 2, maximum_contenders_to_consider: 100, + minimum_pool_notes_for_outgoing: 250, + shielded_proof_verification_fee: 100_000_000, + shielded_per_action_processing_fee: 3_000_000, }, }; diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v2.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v2.rs index e3fa0aa7649..4e6dbad38e7 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v2.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v2.rs @@ -200,10 +200,52 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V2: DriveAbciValidationVersions = state: 0, transform_into_action: 0, }, + shield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_transfer_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + unshield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shield_from_asset_lock_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_withdrawal_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, }, has_nonce_validation: 0, has_address_witness_validation: 0, validate_address_witnesses: 0, + validate_shielded_proof: 0, + validate_minimum_shielded_fee: 0, process_state_transition: 0, state_transition_to_execution_event_for_check_tx: 0, penalties: PenaltyAmounts { @@ -212,9 +254,13 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V2: DriveAbciValidationVersions = validation_of_added_keys_structure_failure: 10000000, validation_of_added_keys_proof_of_possession_failure: 50000000, address_funds_insufficient_balance: 10000000, + shielded_proof_verification_failure: 50000000, }, event_constants: DriveAbciValidationConstants { maximum_vote_polls_to_process: 2, maximum_contenders_to_consider: 100, + minimum_pool_notes_for_outgoing: 250, + shielded_proof_verification_fee: 100_000_000, + shielded_per_action_processing_fee: 3_000_000, }, }; diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v3.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v3.rs index 2fd5cc1d3bd..2d72c6cc872 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v3.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v3.rs @@ -200,10 +200,52 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V3: DriveAbciValidationVersions = state: 0, transform_into_action: 0, }, + shield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_transfer_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + unshield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shield_from_asset_lock_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_withdrawal_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, }, has_nonce_validation: 0, has_address_witness_validation: 0, validate_address_witnesses: 0, + validate_shielded_proof: 0, + validate_minimum_shielded_fee: 0, process_state_transition: 0, state_transition_to_execution_event_for_check_tx: 0, penalties: PenaltyAmounts { @@ -212,9 +254,13 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V3: DriveAbciValidationVersions = validation_of_added_keys_structure_failure: 10000000, validation_of_added_keys_proof_of_possession_failure: 50000000, address_funds_insufficient_balance: 10000000, + shielded_proof_verification_failure: 50000000, }, event_constants: DriveAbciValidationConstants { maximum_vote_polls_to_process: 2, maximum_contenders_to_consider: 100, + minimum_pool_notes_for_outgoing: 250, + shielded_proof_verification_fee: 100_000_000, + shielded_per_action_processing_fee: 3_000_000, }, }; diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v4.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v4.rs index 796f8615dca..3af5abca025 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v4.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v4.rs @@ -203,10 +203,52 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V4: DriveAbciValidationVersions = state: 0, transform_into_action: 0, }, + shield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_transfer_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + unshield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shield_from_asset_lock_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_withdrawal_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, }, has_nonce_validation: 1, // <---- changed this has_address_witness_validation: 0, validate_address_witnesses: 0, + validate_shielded_proof: 0, + validate_minimum_shielded_fee: 0, process_state_transition: 0, state_transition_to_execution_event_for_check_tx: 0, penalties: PenaltyAmounts { @@ -215,9 +257,13 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V4: DriveAbciValidationVersions = validation_of_added_keys_structure_failure: 10000000, validation_of_added_keys_proof_of_possession_failure: 50000000, address_funds_insufficient_balance: 10000000, + shielded_proof_verification_failure: 50000000, }, event_constants: DriveAbciValidationConstants { maximum_vote_polls_to_process: 2, maximum_contenders_to_consider: 100, + minimum_pool_notes_for_outgoing: 250, + shielded_proof_verification_fee: 100_000_000, + shielded_per_action_processing_fee: 3_000_000, }, }; diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v5.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v5.rs index c9d4e774c33..8d207dd4a26 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v5.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v5.rs @@ -204,10 +204,52 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V5: DriveAbciValidationVersions = state: 0, transform_into_action: 0, }, + shield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_transfer_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + unshield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shield_from_asset_lock_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_withdrawal_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, }, has_nonce_validation: 1, has_address_witness_validation: 0, validate_address_witnesses: 0, + validate_shielded_proof: 0, + validate_minimum_shielded_fee: 0, process_state_transition: 0, state_transition_to_execution_event_for_check_tx: 0, penalties: PenaltyAmounts { @@ -216,9 +258,13 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V5: DriveAbciValidationVersions = validation_of_added_keys_structure_failure: 10000000, validation_of_added_keys_proof_of_possession_failure: 50000000, address_funds_insufficient_balance: 10000000, + shielded_proof_verification_failure: 50000000, }, event_constants: DriveAbciValidationConstants { maximum_vote_polls_to_process: 2, maximum_contenders_to_consider: 100, + minimum_pool_notes_for_outgoing: 250, + shielded_proof_verification_fee: 100_000_000, + shielded_per_action_processing_fee: 3_000_000, }, }; diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v6.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v6.rs index 3b656d6c1e4..e6851d78904 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v6.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v6.rs @@ -207,10 +207,52 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V6: DriveAbciValidationVersions = state: 0, transform_into_action: 0, }, + shield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_transfer_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + unshield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shield_from_asset_lock_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_withdrawal_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, }, has_nonce_validation: 1, has_address_witness_validation: 0, validate_address_witnesses: 0, + validate_shielded_proof: 0, + validate_minimum_shielded_fee: 0, process_state_transition: 0, state_transition_to_execution_event_for_check_tx: 0, penalties: PenaltyAmounts { @@ -219,9 +261,13 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V6: DriveAbciValidationVersions = validation_of_added_keys_structure_failure: 10000000, validation_of_added_keys_proof_of_possession_failure: 50000000, address_funds_insufficient_balance: 10000000, + shielded_proof_verification_failure: 50000000, }, event_constants: DriveAbciValidationConstants { maximum_vote_polls_to_process: 2, maximum_contenders_to_consider: 100, + minimum_pool_notes_for_outgoing: 250, + shielded_proof_verification_fee: 100_000_000, + shielded_per_action_processing_fee: 3_000_000, }, }; diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v7.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v7.rs index 5f2ceb620c7..c8900b408b6 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v7.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v7.rs @@ -201,10 +201,52 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V7: DriveAbciValidationVersions = state: 0, transform_into_action: 0, }, + shield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: Some(0), + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_transfer_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: Some(0), + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + unshield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: Some(0), + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shield_from_asset_lock_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_withdrawal_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, }, has_nonce_validation: 1, has_address_witness_validation: 0, validate_address_witnesses: 0, + validate_shielded_proof: 0, + validate_minimum_shielded_fee: 0, process_state_transition: 0, state_transition_to_execution_event_for_check_tx: 0, penalties: PenaltyAmounts { @@ -213,9 +255,13 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V7: DriveAbciValidationVersions = validation_of_added_keys_structure_failure: 10000000, validation_of_added_keys_proof_of_possession_failure: 50000000, address_funds_insufficient_balance: 10000000, + shielded_proof_verification_failure: 50000000, }, event_constants: DriveAbciValidationConstants { maximum_vote_polls_to_process: 2, maximum_contenders_to_consider: 100, + minimum_pool_notes_for_outgoing: 250, + shielded_proof_verification_fee: 100_000_000, + shielded_per_action_processing_fee: 3_000_000, }, }; diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v8.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v8.rs index 421541526c8..a9936441ff8 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v8.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v8.rs @@ -205,10 +205,52 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V8: DriveAbciValidationVersions = state: 0, transform_into_action: 0, }, + shield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: Some(0), + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_transfer_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: Some(0), + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + unshield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: Some(0), + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shield_from_asset_lock_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_withdrawal_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, }, has_nonce_validation: 1, has_address_witness_validation: 0, validate_address_witnesses: 0, + validate_shielded_proof: 0, + validate_minimum_shielded_fee: 0, process_state_transition: 0, state_transition_to_execution_event_for_check_tx: 0, penalties: PenaltyAmounts { @@ -217,9 +259,13 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V8: DriveAbciValidationVersions = validation_of_added_keys_structure_failure: 10000000, validation_of_added_keys_proof_of_possession_failure: 50000000, address_funds_insufficient_balance: 10000000, + shielded_proof_verification_failure: 50000000, }, event_constants: DriveAbciValidationConstants { maximum_vote_polls_to_process: 2, maximum_contenders_to_consider: 100, + minimum_pool_notes_for_outgoing: 250, + shielded_proof_verification_fee: 100_000_000, + shielded_per_action_processing_fee: 3_000_000, }, }; diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_group_method_versions/mod.rs b/packages/rs-platform-version/src/version/drive_versions/drive_group_method_versions/mod.rs index 4766055d3af..0d7a67b58f7 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_group_method_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_group_method_versions/mod.rs @@ -63,3 +63,11 @@ pub struct DriveGroupCostEstimationMethodVersions { pub struct DriveAddressFundsCostEstimationMethodVersions { pub for_address_balance_update: FeatureVersion, } + +#[derive(Clone, Debug, Default)] +pub struct DriveShieldedMethodVersions { + pub prove_nullifiers_trunk_query: FeatureVersion, + pub prove_nullifiers_branch_query: FeatureVersion, + pub nullifiers_query_min_depth: u8, + pub nullifiers_query_max_depth: u8, +} diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_grove_method_versions/mod.rs b/packages/rs-platform-version/src/version/drive_versions/drive_grove_method_versions/mod.rs index dd0d2b64b6b..4c0712225c8 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_grove_method_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_grove_method_versions/mod.rs @@ -38,6 +38,8 @@ pub struct DriveGroveBasicMethodVersions { pub grove_get_optional_sum_tree_total_value: FeatureVersion, pub grove_get_raw_optional_item: FeatureVersion, pub grove_get_big_sum_tree_total_value: FeatureVersion, + pub grove_get_proved_path_query_v1: FeatureVersion, + pub grove_commitment_tree_count: FeatureVersion, } #[derive(Clone, Debug, Default)] @@ -60,6 +62,7 @@ pub struct DriveGroveBatchMethodVersions { pub batch_insert_empty_sum_tree: FeatureVersion, pub batch_move: FeatureVersion, pub batch_insert_item_with_sum_item_if_not_exists: FeatureVersion, + pub batch_insert_auto_incremented_items_in_count_tree: FeatureVersion, } #[derive(Clone, Debug, Default)] diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_grove_method_versions/v1.rs b/packages/rs-platform-version/src/version/drive_versions/drive_grove_method_versions/v1.rs index f6376001473..2df9f00f055 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_grove_method_versions/v1.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_grove_method_versions/v1.rs @@ -31,6 +31,8 @@ pub const DRIVE_GROVE_METHOD_VERSIONS_V1: DriveGroveMethodVersions = DriveGroveM grove_get_optional_sum_tree_total_value: 0, grove_get_raw_optional_item: 0, grove_get_big_sum_tree_total_value: 0, + grove_get_proved_path_query_v1: 0, + grove_commitment_tree_count: 0, }, batch: DriveGroveBatchMethodVersions { batch_insert_empty_tree: 0, @@ -51,6 +53,7 @@ pub const DRIVE_GROVE_METHOD_VERSIONS_V1: DriveGroveMethodVersions = DriveGroveM batch_insert_empty_sum_tree: 0, batch_move: 0, batch_insert_item_with_sum_item_if_not_exists: 0, + batch_insert_auto_incremented_items_in_count_tree: 0, }, apply: DriveGroveApplyMethodVersions { grove_apply_operation: 0, diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_state_transition_method_versions/mod.rs b/packages/rs-platform-version/src/version/drive_versions/drive_state_transition_method_versions/mod.rs index 5391ca01e1f..9c1ec2942ba 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_state_transition_method_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_state_transition_method_versions/mod.rs @@ -48,6 +48,11 @@ pub struct DriveStateTransitionActionConvertToHighLevelOperationsMethodVersions pub address_funds_transfer_transition: FeatureVersion, pub address_credit_withdrawal_transition: FeatureVersion, pub address_funding_from_asset_lock_transition: FeatureVersion, + pub shield_transition: FeatureVersion, + pub shield_from_asset_lock_transition: FeatureVersion, + pub shielded_transfer_transition: FeatureVersion, + pub unshield_transition: FeatureVersion, + pub shielded_withdrawal_transition: FeatureVersion, } #[derive(Clone, Debug, Default)] diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_state_transition_method_versions/v1.rs b/packages/rs-platform-version/src/version/drive_versions/drive_state_transition_method_versions/v1.rs index 0e1b6602175..0a9930b133b 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_state_transition_method_versions/v1.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_state_transition_method_versions/v1.rs @@ -49,5 +49,10 @@ pub const DRIVE_STATE_TRANSITION_METHOD_VERSIONS_V1: DriveStateTransitionMethodV address_funds_transfer_transition: 0, address_credit_withdrawal_transition: 0, address_funding_from_asset_lock_transition: 0, + shield_transition: 0, + shield_from_asset_lock_transition: 0, + shielded_transfer_transition: 0, + unshield_transition: 0, + shielded_withdrawal_transition: 0, }, }; diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_state_transition_method_versions/v2.rs b/packages/rs-platform-version/src/version/drive_versions/drive_state_transition_method_versions/v2.rs index 29e7442262a..7bc67bbbbde 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_state_transition_method_versions/v2.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_state_transition_method_versions/v2.rs @@ -50,5 +50,10 @@ pub const DRIVE_STATE_TRANSITION_METHOD_VERSIONS_V2: DriveStateTransitionMethodV address_funds_transfer_transition: 0, address_credit_withdrawal_transition: 0, address_funding_from_asset_lock_transition: 0, + shield_transition: 0, + shield_from_asset_lock_transition: 0, + shielded_transfer_transition: 0, + unshield_transition: 0, + shielded_withdrawal_transition: 0, }, }; diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/mod.rs b/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/mod.rs index 8e979270ce8..a5448463776 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/mod.rs @@ -14,6 +14,19 @@ pub struct DriveVerifyMethodVersions { pub voting: DriveVerifyVoteMethodVersions, pub address_funds: DriveVerifyAddressFundsMethodVersions, pub state_transition: DriveVerifyStateTransitionMethodVersions, + pub shielded: DriveVerifyShieldedMethodVersions, +} + +#[derive(Clone, Debug, Default)] +pub struct DriveVerifyShieldedMethodVersions { + pub verify_shielded_pool_state: FeatureVersion, + pub verify_shielded_anchors: FeatureVersion, + pub verify_shielded_encrypted_notes: FeatureVersion, + pub verify_shielded_nullifiers: FeatureVersion, + pub verify_nullifiers_trunk_query: FeatureVersion, + pub verify_nullifiers_branch_query: FeatureVersion, + pub verify_recent_nullifier_changes: FeatureVersion, + pub verify_compacted_nullifier_changes: FeatureVersion, } #[derive(Clone, Debug, Default)] diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/v1.rs b/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/v1.rs index 7c255e83a78..1ad8c7d77cb 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/v1.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/v1.rs @@ -2,8 +2,9 @@ use crate::version::drive_versions::drive_verify_method_versions::{ DriveVerifyAddressFundsMethodVersions, DriveVerifyContractMethodVersions, DriveVerifyDocumentMethodVersions, DriveVerifyGroupMethodVersions, DriveVerifyIdentityMethodVersions, DriveVerifyMethodVersions, - DriveVerifySingleDocumentMethodVersions, DriveVerifyStateTransitionMethodVersions, - DriveVerifySystemMethodVersions, DriveVerifyTokenMethodVersions, DriveVerifyVoteMethodVersions, + DriveVerifyShieldedMethodVersions, DriveVerifySingleDocumentMethodVersions, + DriveVerifyStateTransitionMethodVersions, DriveVerifySystemMethodVersions, + DriveVerifyTokenMethodVersions, DriveVerifyVoteMethodVersions, }; pub const DRIVE_VERIFY_METHOD_VERSIONS_V1: DriveVerifyMethodVersions = DriveVerifyMethodVersions { @@ -90,4 +91,14 @@ pub const DRIVE_VERIFY_METHOD_VERSIONS_V1: DriveVerifyMethodVersions = DriveVeri state_transition: DriveVerifyStateTransitionMethodVersions { verify_state_transition_was_executed_with_proof: 0, }, + shielded: DriveVerifyShieldedMethodVersions { + verify_shielded_pool_state: 0, + verify_shielded_anchors: 0, + verify_shielded_encrypted_notes: 0, + verify_shielded_nullifiers: 0, + verify_nullifiers_trunk_query: 0, + verify_nullifiers_branch_query: 0, + verify_recent_nullifier_changes: 0, + verify_compacted_nullifier_changes: 0, + }, }; diff --git a/packages/rs-platform-version/src/version/drive_versions/mod.rs b/packages/rs-platform-version/src/version/drive_versions/mod.rs index e7eba236c55..2f547c3c391 100644 --- a/packages/rs-platform-version/src/version/drive_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_versions/mod.rs @@ -1,4 +1,6 @@ -use crate::version::drive_versions::drive_group_method_versions::DriveAddressFundsMethodVersions; +use crate::version::drive_versions::drive_group_method_versions::{ + DriveAddressFundsMethodVersions, DriveShieldedMethodVersions, +}; use crate::version::FeatureVersion; use drive_contract_method_versions::DriveContractMethodVersions; use drive_credit_pool_method_versions::DriveCreditPoolMethodVersions; @@ -31,6 +33,7 @@ pub mod v3; pub mod v4; pub mod v5; pub mod v6; +pub mod v7; #[derive(Clone, Debug, Default)] pub struct DriveVersion { @@ -65,6 +68,7 @@ pub struct DriveMethodVersions { pub platform_state: DrivePlatformStateMethodVersions, pub group: DriveGroupMethodVersions, pub address_funds: DriveAddressFundsMethodVersions, + pub shielded: DriveShieldedMethodVersions, pub saved_block_transactions: DriveSavedBlockTransactionsMethodVersions, } @@ -84,6 +88,14 @@ pub struct DriveSavedBlockTransactionsMethodVersions { pub max_blocks_before_compaction: u16, /// Maximum number of address balance entries before compaction is triggered pub max_addresses_before_compaction: u32, + pub store_nullifiers: FeatureVersion, + pub fetch_nullifiers: FeatureVersion, + pub compact_nullifiers: FeatureVersion, + pub cleanup_expired_nullifiers: FeatureVersion, + /// Maximum number of blocks to store before nullifier compaction is triggered + pub max_blocks_before_nullifier_compaction: u16, + /// Maximum number of nullifier entries before compaction is triggered + pub max_nullifiers_before_compaction: u32, } #[derive(Clone, Debug, Default)] diff --git a/packages/rs-platform-version/src/version/drive_versions/v1.rs b/packages/rs-platform-version/src/version/drive_versions/v1.rs index e6c0a2722db..227c6dbb5ca 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v1.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v1.rs @@ -3,6 +3,7 @@ use crate::version::drive_versions::drive_contract_method_versions::v1::DRIVE_CO use crate::version::drive_versions::drive_credit_pool_method_versions::v1::CREDIT_POOL_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_document_method_versions::v1::DRIVE_DOCUMENT_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_group_method_versions::v1::DRIVE_GROUP_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_group_method_versions::DriveShieldedMethodVersions; use crate::version::drive_versions::drive_grove_method_versions::v1::DRIVE_GROVE_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_identity_method_versions::v1::DRIVE_IDENTITY_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_state_transition_method_versions::v1::DRIVE_STATE_TRANSITION_METHOD_VERSIONS_V1; @@ -103,6 +104,12 @@ pub const DRIVE_VERSION_V1: DriveVersion = DriveVersion { }, group: DRIVE_GROUP_METHOD_VERSIONS_V1, address_funds: DRIVE_ADDRESS_FUNDS_METHOD_VERSIONS_V1, + shielded: DriveShieldedMethodVersions { + prove_nullifiers_trunk_query: 0, + prove_nullifiers_branch_query: 0, + nullifiers_query_min_depth: 6, + nullifiers_query_max_depth: 10, + }, saved_block_transactions: DriveSavedBlockTransactionsMethodVersions { store_address_balances: 0, fetch_address_balances: 0, @@ -110,6 +117,12 @@ pub const DRIVE_VERSION_V1: DriveVersion = DriveVersion { cleanup_expired_address_balances: 0, max_blocks_before_compaction: 64, max_addresses_before_compaction: 2048, + store_nullifiers: 0, + fetch_nullifiers: 0, + compact_nullifiers: 0, + cleanup_expired_nullifiers: 0, + max_blocks_before_nullifier_compaction: 64, + max_nullifiers_before_compaction: 2048, }, }, grove_methods: DRIVE_GROVE_METHOD_VERSIONS_V1, diff --git a/packages/rs-platform-version/src/version/drive_versions/v2.rs b/packages/rs-platform-version/src/version/drive_versions/v2.rs index b8dce7adf7f..203f1afa59f 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v2.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v2.rs @@ -3,6 +3,7 @@ use crate::version::drive_versions::drive_contract_method_versions::v1::DRIVE_CO use crate::version::drive_versions::drive_credit_pool_method_versions::v1::CREDIT_POOL_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_document_method_versions::v1::DRIVE_DOCUMENT_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_group_method_versions::v1::DRIVE_GROUP_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_group_method_versions::DriveShieldedMethodVersions; use crate::version::drive_versions::drive_grove_method_versions::v1::DRIVE_GROVE_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_identity_method_versions::v1::DRIVE_IDENTITY_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_state_transition_method_versions::v1::DRIVE_STATE_TRANSITION_METHOD_VERSIONS_V1; @@ -103,6 +104,12 @@ pub const DRIVE_VERSION_V2: DriveVersion = DriveVersion { }, group: DRIVE_GROUP_METHOD_VERSIONS_V1, address_funds: DRIVE_ADDRESS_FUNDS_METHOD_VERSIONS_V1, + shielded: DriveShieldedMethodVersions { + prove_nullifiers_trunk_query: 0, + prove_nullifiers_branch_query: 0, + nullifiers_query_min_depth: 6, + nullifiers_query_max_depth: 10, + }, saved_block_transactions: DriveSavedBlockTransactionsMethodVersions { store_address_balances: 0, fetch_address_balances: 0, @@ -110,6 +117,12 @@ pub const DRIVE_VERSION_V2: DriveVersion = DriveVersion { cleanup_expired_address_balances: 0, max_blocks_before_compaction: 64, max_addresses_before_compaction: 2048, + store_nullifiers: 0, + fetch_nullifiers: 0, + compact_nullifiers: 0, + cleanup_expired_nullifiers: 0, + max_blocks_before_nullifier_compaction: 64, + max_nullifiers_before_compaction: 2048, }, }, grove_methods: DRIVE_GROVE_METHOD_VERSIONS_V1, diff --git a/packages/rs-platform-version/src/version/drive_versions/v3.rs b/packages/rs-platform-version/src/version/drive_versions/v3.rs index d48d17bc84f..408a49cab6e 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v3.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v3.rs @@ -3,6 +3,7 @@ use crate::version::drive_versions::drive_contract_method_versions::v1::DRIVE_CO use crate::version::drive_versions::drive_credit_pool_method_versions::v1::CREDIT_POOL_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_document_method_versions::v1::DRIVE_DOCUMENT_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_group_method_versions::v1::DRIVE_GROUP_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_group_method_versions::DriveShieldedMethodVersions; use crate::version::drive_versions::drive_grove_method_versions::v1::DRIVE_GROVE_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_identity_method_versions::v1::DRIVE_IDENTITY_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_state_transition_method_versions::v1::DRIVE_STATE_TRANSITION_METHOD_VERSIONS_V1; @@ -103,6 +104,12 @@ pub const DRIVE_VERSION_V3: DriveVersion = DriveVersion { }, group: DRIVE_GROUP_METHOD_VERSIONS_V1, address_funds: DRIVE_ADDRESS_FUNDS_METHOD_VERSIONS_V1, + shielded: DriveShieldedMethodVersions { + prove_nullifiers_trunk_query: 0, + prove_nullifiers_branch_query: 0, + nullifiers_query_min_depth: 6, + nullifiers_query_max_depth: 10, + }, saved_block_transactions: DriveSavedBlockTransactionsMethodVersions { store_address_balances: 0, fetch_address_balances: 0, @@ -110,6 +117,12 @@ pub const DRIVE_VERSION_V3: DriveVersion = DriveVersion { cleanup_expired_address_balances: 0, max_blocks_before_compaction: 64, max_addresses_before_compaction: 2048, + store_nullifiers: 0, + fetch_nullifiers: 0, + compact_nullifiers: 0, + cleanup_expired_nullifiers: 0, + max_blocks_before_nullifier_compaction: 64, + max_nullifiers_before_compaction: 2048, }, }, grove_methods: DRIVE_GROVE_METHOD_VERSIONS_V1, diff --git a/packages/rs-platform-version/src/version/drive_versions/v4.rs b/packages/rs-platform-version/src/version/drive_versions/v4.rs index 510942261b6..4ad9c1ee18c 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v4.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v4.rs @@ -3,6 +3,7 @@ use crate::version::drive_versions::drive_contract_method_versions::v2::DRIVE_CO use crate::version::drive_versions::drive_credit_pool_method_versions::v1::CREDIT_POOL_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_document_method_versions::v1::DRIVE_DOCUMENT_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_group_method_versions::v1::DRIVE_GROUP_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_group_method_versions::DriveShieldedMethodVersions; use crate::version::drive_versions::drive_grove_method_versions::v1::DRIVE_GROVE_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_identity_method_versions::v1::DRIVE_IDENTITY_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_state_transition_method_versions::v1::DRIVE_STATE_TRANSITION_METHOD_VERSIONS_V1; @@ -103,6 +104,12 @@ pub const DRIVE_VERSION_V4: DriveVersion = DriveVersion { }, group: DRIVE_GROUP_METHOD_VERSIONS_V1, address_funds: DRIVE_ADDRESS_FUNDS_METHOD_VERSIONS_V1, + shielded: DriveShieldedMethodVersions { + prove_nullifiers_trunk_query: 0, + prove_nullifiers_branch_query: 0, + nullifiers_query_min_depth: 6, + nullifiers_query_max_depth: 10, + }, saved_block_transactions: DriveSavedBlockTransactionsMethodVersions { store_address_balances: 0, fetch_address_balances: 0, @@ -110,6 +117,12 @@ pub const DRIVE_VERSION_V4: DriveVersion = DriveVersion { cleanup_expired_address_balances: 0, max_blocks_before_compaction: 64, max_addresses_before_compaction: 2048, + store_nullifiers: 0, + fetch_nullifiers: 0, + compact_nullifiers: 0, + cleanup_expired_nullifiers: 0, + max_blocks_before_nullifier_compaction: 64, + max_nullifiers_before_compaction: 2048, }, }, grove_methods: DRIVE_GROVE_METHOD_VERSIONS_V1, diff --git a/packages/rs-platform-version/src/version/drive_versions/v5.rs b/packages/rs-platform-version/src/version/drive_versions/v5.rs index 76710f4a9f7..85808b0f126 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v5.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v5.rs @@ -3,6 +3,7 @@ use crate::version::drive_versions::drive_contract_method_versions::v2::DRIVE_CO use crate::version::drive_versions::drive_credit_pool_method_versions::v1::CREDIT_POOL_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_document_method_versions::v2::DRIVE_DOCUMENT_METHOD_VERSIONS_V2; use crate::version::drive_versions::drive_group_method_versions::v1::DRIVE_GROUP_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_group_method_versions::DriveShieldedMethodVersions; use crate::version::drive_versions::drive_grove_method_versions::v1::DRIVE_GROVE_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_identity_method_versions::v1::DRIVE_IDENTITY_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_state_transition_method_versions::v1::DRIVE_STATE_TRANSITION_METHOD_VERSIONS_V1; @@ -105,6 +106,12 @@ pub const DRIVE_VERSION_V5: DriveVersion = DriveVersion { }, group: DRIVE_GROUP_METHOD_VERSIONS_V1, address_funds: DRIVE_ADDRESS_FUNDS_METHOD_VERSIONS_V1, + shielded: DriveShieldedMethodVersions { + prove_nullifiers_trunk_query: 0, + prove_nullifiers_branch_query: 0, + nullifiers_query_min_depth: 6, + nullifiers_query_max_depth: 10, + }, saved_block_transactions: DriveSavedBlockTransactionsMethodVersions { store_address_balances: 0, fetch_address_balances: 0, @@ -112,6 +119,12 @@ pub const DRIVE_VERSION_V5: DriveVersion = DriveVersion { cleanup_expired_address_balances: 0, max_blocks_before_compaction: 64, max_addresses_before_compaction: 2048, + store_nullifiers: 0, + fetch_nullifiers: 0, + compact_nullifiers: 0, + cleanup_expired_nullifiers: 0, + max_blocks_before_nullifier_compaction: 64, + max_nullifiers_before_compaction: 2048, }, }, grove_methods: DRIVE_GROVE_METHOD_VERSIONS_V1, diff --git a/packages/rs-platform-version/src/version/drive_versions/v6.rs b/packages/rs-platform-version/src/version/drive_versions/v6.rs index 75131aba245..768caeb5580 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v6.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v6.rs @@ -3,6 +3,7 @@ use crate::version::drive_versions::drive_contract_method_versions::v2::DRIVE_CO use crate::version::drive_versions::drive_credit_pool_method_versions::v1::CREDIT_POOL_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_document_method_versions::v2::DRIVE_DOCUMENT_METHOD_VERSIONS_V2; use crate::version::drive_versions::drive_group_method_versions::v1::DRIVE_GROUP_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_group_method_versions::DriveShieldedMethodVersions; use crate::version::drive_versions::drive_grove_method_versions::v1::DRIVE_GROVE_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_identity_method_versions::v1::DRIVE_IDENTITY_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_state_transition_method_versions::v2::DRIVE_STATE_TRANSITION_METHOD_VERSIONS_V2; @@ -107,6 +108,12 @@ pub const DRIVE_VERSION_V6: DriveVersion = DriveVersion { }, group: DRIVE_GROUP_METHOD_VERSIONS_V1, address_funds: DRIVE_ADDRESS_FUNDS_METHOD_VERSIONS_V1, + shielded: DriveShieldedMethodVersions { + prove_nullifiers_trunk_query: 0, + prove_nullifiers_branch_query: 0, + nullifiers_query_min_depth: 6, + nullifiers_query_max_depth: 10, + }, saved_block_transactions: DriveSavedBlockTransactionsMethodVersions { store_address_balances: 0, fetch_address_balances: 0, @@ -114,6 +121,12 @@ pub const DRIVE_VERSION_V6: DriveVersion = DriveVersion { cleanup_expired_address_balances: 0, max_blocks_before_compaction: 64, max_addresses_before_compaction: 2048, + store_nullifiers: 0, + fetch_nullifiers: 0, + compact_nullifiers: 0, + cleanup_expired_nullifiers: 0, + max_blocks_before_nullifier_compaction: 64, + max_nullifiers_before_compaction: 2048, }, }, grove_methods: DRIVE_GROVE_METHOD_VERSIONS_V1, diff --git a/packages/rs-platform-version/src/version/drive_versions/v7.rs b/packages/rs-platform-version/src/version/drive_versions/v7.rs new file mode 100644 index 00000000000..f0af92ec592 --- /dev/null +++ b/packages/rs-platform-version/src/version/drive_versions/v7.rs @@ -0,0 +1,131 @@ +use crate::version::drive_versions::drive_address_funds_method_versions::v1::DRIVE_ADDRESS_FUNDS_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_contract_method_versions::v2::DRIVE_CONTRACT_METHOD_VERSIONS_V2; +use crate::version::drive_versions::drive_credit_pool_method_versions::v1::CREDIT_POOL_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_document_method_versions::v2::DRIVE_DOCUMENT_METHOD_VERSIONS_V2; +use crate::version::drive_versions::drive_group_method_versions::v1::DRIVE_GROUP_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_group_method_versions::DriveShieldedMethodVersions; +use crate::version::drive_versions::drive_grove_method_versions::v1::DRIVE_GROVE_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_identity_method_versions::v1::DRIVE_IDENTITY_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_state_transition_method_versions::v2::DRIVE_STATE_TRANSITION_METHOD_VERSIONS_V2; +use crate::version::drive_versions::drive_structure_version::v1::DRIVE_STRUCTURE_V1; +use crate::version::drive_versions::drive_token_method_versions::v1::DRIVE_TOKEN_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_verify_method_versions::v1::DRIVE_VERIFY_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_vote_method_versions::v2::DRIVE_VOTE_METHOD_VERSIONS_V2; +use crate::version::drive_versions::{ + DriveAssetLockMethodVersions, DriveBalancesMethodVersions, DriveBatchOperationsMethodVersion, + DriveEstimatedCostsMethodVersions, DriveFeesMethodVersions, DriveFetchMethodVersions, + DriveInitializationMethodVersions, DriveMethodVersions, DriveOperationsMethodVersion, + DrivePlatformStateMethodVersions, DrivePlatformSystemMethodVersions, + DrivePrefundedSpecializedMethodVersions, DriveProtocolUpgradeVersions, + DriveProveMethodVersions, DriveSavedBlockTransactionsMethodVersions, + DriveSystemEstimationCostsMethodVersions, DriveVersion, +}; +use grovedb_version::version::v2::GROVE_V2; + +/// This was introduced in protocol v12 for shielded transactions. +pub const DRIVE_VERSION_V7: DriveVersion = DriveVersion { + structure: DRIVE_STRUCTURE_V1, + methods: DriveMethodVersions { + initialization: DriveInitializationMethodVersions { + create_initial_state_structure: 3, + }, + credit_pools: CREDIT_POOL_METHOD_VERSIONS_V1, + protocol_upgrade: DriveProtocolUpgradeVersions { + clear_version_information: 0, + fetch_versions_with_counter: 0, + fetch_proved_versions_with_counter: 0, + fetch_validator_version_votes: 0, + fetch_proved_validator_version_votes: 0, + remove_validators_proposed_app_versions: 0, + update_validator_proposed_app_version: 0, + }, + prove: DriveProveMethodVersions { + prove_elements: 0, + prove_multiple_state_transition_results: 0, + prove_state_transition: 0, + }, + balances: DriveBalancesMethodVersions { + add_to_system_credits: 0, + add_to_system_credits_operations: 0, + remove_from_system_credits: 0, + remove_from_system_credits_operations: 0, + calculate_total_credits_balance: 1, // Changed because we now add the address trees + }, + document: DRIVE_DOCUMENT_METHOD_VERSIONS_V2, // Changed + vote: DRIVE_VOTE_METHOD_VERSIONS_V2, + contract: DRIVE_CONTRACT_METHOD_VERSIONS_V2, + fees: DriveFeesMethodVersions { calculate_fee: 0 }, + estimated_costs: DriveEstimatedCostsMethodVersions { + add_estimation_costs_for_levels_up_to_contract: 0, + add_estimation_costs_for_levels_up_to_contract_document_type_excluded: 0, + add_estimation_costs_for_contested_document_tree_levels_up_to_contract: 0, + add_estimation_costs_for_contested_document_tree_levels_up_to_contract_document_type_excluded: 0, + }, + asset_lock: DriveAssetLockMethodVersions { + add_asset_lock_outpoint: 0, + add_estimation_costs_for_adding_asset_lock: 0, + fetch_asset_lock_outpoint_info: 0, + }, + verify: DRIVE_VERIFY_METHOD_VERSIONS_V1, + identity: DRIVE_IDENTITY_METHOD_VERSIONS_V1, + token: DRIVE_TOKEN_METHOD_VERSIONS_V1, + platform_system: DrivePlatformSystemMethodVersions { + estimation_costs: DriveSystemEstimationCostsMethodVersions { + for_total_system_credits_update: 0, + }, + }, + operations: DriveOperationsMethodVersion { + rollback_transaction: 0, + drop_cache: 0, + commit_transaction: 0, + apply_partial_batch_low_level_drive_operations: 0, + apply_partial_batch_grovedb_operations: 0, + apply_batch_low_level_drive_operations: 0, + apply_batch_grovedb_operations: 0, + }, + state_transitions: DRIVE_STATE_TRANSITION_METHOD_VERSIONS_V2, //changed + batch_operations: DriveBatchOperationsMethodVersion { + convert_drive_operations_to_grove_operations: 0, + apply_drive_operations: 0, + }, + platform_state: DrivePlatformStateMethodVersions { + fetch_platform_state_bytes: 0, + store_platform_state_bytes: 0, + }, + fetch: DriveFetchMethodVersions { fetch_elements: 0 }, + prefunded_specialized_balances: DrivePrefundedSpecializedMethodVersions { + fetch_single: 0, + prove_single: 0, + add_prefunded_specialized_balance: 0, + add_prefunded_specialized_balance_operations: 1, + deduct_from_prefunded_specialized_balance: 1, + deduct_from_prefunded_specialized_balance_operations: 0, + estimated_cost_for_prefunded_specialized_balance_update: 0, + empty_prefunded_specialized_balance: 0, + }, + group: DRIVE_GROUP_METHOD_VERSIONS_V1, + address_funds: DRIVE_ADDRESS_FUNDS_METHOD_VERSIONS_V1, + shielded: DriveShieldedMethodVersions { + prove_nullifiers_trunk_query: 0, + prove_nullifiers_branch_query: 0, + nullifiers_query_min_depth: 6, + nullifiers_query_max_depth: 10, + }, + saved_block_transactions: DriveSavedBlockTransactionsMethodVersions { + store_address_balances: 0, + fetch_address_balances: 0, + compact_address_balances: 0, + cleanup_expired_address_balances: 0, + max_blocks_before_compaction: 64, + max_addresses_before_compaction: 2048, + store_nullifiers: 0, + fetch_nullifiers: 0, + compact_nullifiers: 0, + cleanup_expired_nullifiers: 0, + max_blocks_before_nullifier_compaction: 64, + max_nullifiers_before_compaction: 2048, + }, + }, + grove_methods: DRIVE_GROVE_METHOD_VERSIONS_V1, + grove_version: GROVE_V2, +}; diff --git a/packages/rs-platform-version/src/version/feature_initial_protocol_versions.rs b/packages/rs-platform-version/src/version/feature_initial_protocol_versions.rs index 4607cf71d20..e004582a10e 100644 --- a/packages/rs-platform-version/src/version/feature_initial_protocol_versions.rs +++ b/packages/rs-platform-version/src/version/feature_initial_protocol_versions.rs @@ -1,3 +1,4 @@ use crate::version::ProtocolVersion; pub const ADDRESS_FUNDS_INITIAL_PROTOCOL_VERSION: ProtocolVersion = 11; +pub const SHIELDED_POOL_INITIAL_PROTOCOL_VERSION: ProtocolVersion = 12; diff --git a/packages/rs-platform-version/src/version/fee/hashing/mod.rs b/packages/rs-platform-version/src/version/fee/hashing/mod.rs index 8e42863d3bb..0a1c42e535f 100644 --- a/packages/rs-platform-version/src/version/fee/hashing/mod.rs +++ b/packages/rs-platform-version/src/version/fee/hashing/mod.rs @@ -10,6 +10,8 @@ pub struct FeeHashingVersion { pub sha256_ripe_md160_base: u64, pub single_sha256_base: u64, pub ripemd160_per_block: u64, + /// Cost per Sinsemilla hash operation (elliptic curve, ~100x more expensive than Blake3) + pub sinsemilla_base: u64, } #[derive(Clone, Debug, Encode, Decode, Default, PartialEq, Eq)] diff --git a/packages/rs-platform-version/src/version/fee/hashing/v1.rs b/packages/rs-platform-version/src/version/fee/hashing/v1.rs index 7bdd3052142..15ff6967eaf 100644 --- a/packages/rs-platform-version/src/version/fee/hashing/v1.rs +++ b/packages/rs-platform-version/src/version/fee/hashing/v1.rs @@ -7,4 +7,5 @@ pub const FEE_HASHING_VERSION1: FeeHashingVersion = FeeHashingVersion { sha256_per_block: 5000, blake3_per_block: 300, ripemd160_per_block: 5000, // RIPEMD160 has 64-byte blocks, similar cost to SHA256 + sinsemilla_base: 40_000, // Sinsemilla is an elliptic curve hash (~100x Blake3) }; diff --git a/packages/rs-platform-version/src/version/mocks/v2_test.rs b/packages/rs-platform-version/src/version/mocks/v2_test.rs index 05610c204f6..e622afc4e0c 100644 --- a/packages/rs-platform-version/src/version/mocks/v2_test.rs +++ b/packages/rs-platform-version/src/version/mocks/v2_test.rs @@ -19,9 +19,9 @@ use crate::version::drive_abci_versions::drive_abci_method_versions::v1::DRIVE_A use crate::version::drive_abci_versions::drive_abci_query_versions::{ DriveAbciQueryAddressFundsVersions, DriveAbciQueryDataContractVersions, DriveAbciQueryGroupVersions, DriveAbciQueryIdentityVersions, - DriveAbciQueryPrefundedSpecializedBalancesVersions, DriveAbciQuerySystemVersions, - DriveAbciQueryTokenVersions, DriveAbciQueryValidatorVersions, DriveAbciQueryVersions, - DriveAbciQueryVotingVersions, + DriveAbciQueryPrefundedSpecializedBalancesVersions, DriveAbciQueryShieldedVersions, + DriveAbciQuerySystemVersions, DriveAbciQueryTokenVersions, DriveAbciQueryValidatorVersions, + DriveAbciQueryVersions, DriveAbciQueryVotingVersions, }; use crate::version::drive_abci_versions::drive_abci_structure_versions::v1::DRIVE_ABCI_STRUCTURE_VERSIONS_V1; use crate::version::drive_abci_versions::drive_abci_validation_versions::v1::DRIVE_ABCI_VALIDATION_VERSIONS_V1; @@ -32,6 +32,7 @@ use crate::version::drive_versions::drive_contract_method_versions::v1::DRIVE_CO use crate::version::drive_versions::drive_credit_pool_method_versions::v1::CREDIT_POOL_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_document_method_versions::v1::DRIVE_DOCUMENT_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_group_method_versions::v1::DRIVE_GROUP_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_group_method_versions::DriveShieldedMethodVersions; use crate::version::drive_versions::drive_grove_method_versions::v1::DRIVE_GROVE_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_identity_method_versions::v1::DRIVE_IDENTITY_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_state_transition_method_versions::v1::DRIVE_STATE_TRANSITION_METHOD_VERSIONS_V1; @@ -141,7 +142,13 @@ pub const TEST_PLATFORM_V2: PlatformVersion = PlatformVersion { }, group: DRIVE_GROUP_METHOD_VERSIONS_V1, address_funds: DRIVE_ADDRESS_FUNDS_METHOD_VERSIONS_V1, - saved_block_transactions: DriveSavedBlockTransactionsMethodVersions { store_address_balances: 0, fetch_address_balances: 0, compact_address_balances: 0, cleanup_expired_address_balances: 0, max_blocks_before_compaction: 64, max_addresses_before_compaction: 2048 }, + shielded: DriveShieldedMethodVersions { + prove_nullifiers_trunk_query: 0, + prove_nullifiers_branch_query: 0, + nullifiers_query_min_depth: 6, + nullifiers_query_max_depth: 10, + }, + saved_block_transactions: DriveSavedBlockTransactionsMethodVersions { store_address_balances: 0, fetch_address_balances: 0, compact_address_balances: 0, cleanup_expired_address_balances: 0, max_blocks_before_compaction: 64, max_addresses_before_compaction: 2048, store_nullifiers: 0, fetch_nullifiers: 0, compact_nullifiers: 0, cleanup_expired_nullifiers: 0, max_blocks_before_nullifier_compaction: 64, max_nullifiers_before_compaction: 2048 }, }, grove_methods: DRIVE_GROVE_METHOD_VERSIONS_V1, grove_version: GROVE_V1, @@ -391,6 +398,49 @@ pub const TEST_PLATFORM_V2: PlatformVersion = PlatformVersion { default_current_version: 0, }, }, + shielded_queries: DriveAbciQueryShieldedVersions { + encrypted_notes: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + anchors: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + pool_state: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + nullifiers: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + nullifiers_trunk_state: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + nullifiers_branch_state: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + recent_nullifier_changes: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + recent_compacted_nullifier_changes: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + max_encrypted_notes_per_query: 2048, + }, address_funds_queries: DriveAbciQueryAddressFundsVersions { addresses_infos: FeatureVersionBounds { min_version: 0, @@ -454,6 +504,7 @@ pub const TEST_PLATFORM_V2: PlatformVersion = PlatformVersion { max_withdrawal_amount: 50_000_000_000_000, max_contract_group_size: 256, max_token_redemption_cycles: 128, + max_shielded_transition_actions: 100, }, consensus: ConsensusVersions { tenderdash_consensus_version: 0, diff --git a/packages/rs-platform-version/src/version/mocks/v3_test.rs b/packages/rs-platform-version/src/version/mocks/v3_test.rs index abb3281b626..4cfc803e1ff 100644 --- a/packages/rs-platform-version/src/version/mocks/v3_test.rs +++ b/packages/rs-platform-version/src/version/mocks/v3_test.rs @@ -142,6 +142,8 @@ pub const TEST_PLATFORM_V3: PlatformVersion = PlatformVersion { validate_fees_of_event: 0, store_address_balances_to_recent_block_storage: None, cleanup_recent_block_storage_address_balances: None, + store_nullifiers_to_recent_block_storage: None, + cleanup_recent_block_storage_nullifiers: None, }, epoch: DriveAbciEpochMethodVersions { gather_epoch_info: 0, @@ -156,6 +158,7 @@ pub const TEST_PLATFORM_V3: PlatformVersion = PlatformVersion { validator_set_update: 0, should_checkpoint: None, update_checkpoints: None, + record_shielded_pool_anchor: None, }, platform_state_storage: DriveAbciPlatformStateStorageMethodVersions { fetch_platform_state: 0, diff --git a/packages/rs-platform-version/src/version/system_limits/mod.rs b/packages/rs-platform-version/src/version/system_limits/mod.rs index 35929bac5de..f5531de6762 100644 --- a/packages/rs-platform-version/src/version/system_limits/mod.rs +++ b/packages/rs-platform-version/src/version/system_limits/mod.rs @@ -18,4 +18,5 @@ pub struct SystemLimits { // For other distributions we much calculate at each cycle the rewards, so we don't want to // do this that much pub max_token_redemption_cycles: u32, + pub max_shielded_transition_actions: u16, } diff --git a/packages/rs-platform-version/src/version/system_limits/v1.rs b/packages/rs-platform-version/src/version/system_limits/v1.rs index 2379b62d6c6..9b11657710c 100644 --- a/packages/rs-platform-version/src/version/system_limits/v1.rs +++ b/packages/rs-platform-version/src/version/system_limits/v1.rs @@ -10,4 +10,5 @@ pub const SYSTEM_LIMITS_V1: SystemLimits = SystemLimits { max_withdrawal_amount: 50_000_000_000_000, //500 Dash max_contract_group_size: 256, max_token_redemption_cycles: 128, + max_shielded_transition_actions: 100, }; diff --git a/packages/rs-platform-version/src/version/v12.rs b/packages/rs-platform-version/src/version/v12.rs index eacb417eca5..b7dd3bd9986 100644 --- a/packages/rs-platform-version/src/version/v12.rs +++ b/packages/rs-platform-version/src/version/v12.rs @@ -21,7 +21,7 @@ use crate::version::drive_abci_versions::drive_abci_structure_versions::v1::DRIV use crate::version::drive_abci_versions::drive_abci_validation_versions::v8::DRIVE_ABCI_VALIDATION_VERSIONS_V8; use crate::version::drive_abci_versions::drive_abci_withdrawal_constants::v2::DRIVE_ABCI_WITHDRAWAL_CONSTANTS_V2; use crate::version::drive_abci_versions::DriveAbciVersion; -use crate::version::drive_versions::v6::DRIVE_VERSION_V6; +use crate::version::drive_versions::v7::DRIVE_VERSION_V7; use crate::version::fee::v2::FEE_VERSION2; use crate::version::protocol_version::PlatformVersion; use crate::version::system_data_contract_versions::v1::SYSTEM_DATA_CONTRACT_VERSIONS_V1; @@ -33,7 +33,7 @@ pub const PROTOCOL_VERSION_12: ProtocolVersion = 12; /// This version is for Platform release 3.1.0 pub const PLATFORM_V12: PlatformVersion = PlatformVersion { protocol_version: PROTOCOL_VERSION_12, - drive: DRIVE_VERSION_V6, + drive: DRIVE_VERSION_V7, drive_abci: DriveAbciVersion { structs: DRIVE_ABCI_STRUCTURE_VERSIONS_V1, methods: DRIVE_ABCI_METHOD_VERSIONS_V7, From 361684596020364802c60b41a19381e445d07e86 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 4 Mar 2026 17:45:46 +0700 Subject: [PATCH 02/30] chore: add todo!() stubs for shielded state transition types in dependent packages Adds compilation stubs across rs-drive, rs-drive-abci, wasm-dpp, wasm-dpp2, and rs-sdk-ffi for the new shielded state transition variants (Shield, ShieldedTransfer, Unshield, ShieldFromAssetLock, ShieldedWithdrawal) and updated grovedb API (sinsemilla_hash_calls, Option, new TreeType variants, GroveDBProof::V1). These will be replaced with real implementations when each package is split out from feat/zk. Co-Authored-By: Claude Opus 4.6 --- Cargo.lock | 494 +++++++++++++++++- .../traits/address_balances_and_nonces.rs | 14 + .../processor/traits/address_witnesses.rs | 14 + .../traits/addresses_minimum_balance.rs | 14 + .../processor/traits/basic_structure.rs | 14 + .../processor/traits/identity_balance.rs | 7 + .../traits/identity_based_signature.rs | 21 + .../processor/traits/identity_nonces.rs | 14 + .../processor/traits/is_allowed.rs | 7 + .../processor/traits/state.rs | 14 + .../state_transition/transformer/mod.rs | 7 + packages/rs-drive/Cargo.toml | 12 +- .../v0/mod.rs | 1 + .../v0/mod.rs | 1 + packages/rs-drive/src/fees/op.rs | 2 + .../prove/prove_state_transition/v0/mod.rs | 7 + .../src/util/batch/grovedb_op_batch/mod.rs | 12 +- .../v0/mod.rs | 9 +- .../grove_insert_empty_tree/v0/mod.rs | 1 + .../v0/mod.rs | 1 + .../v0/mod.rs | 1 + .../v0/mod.rs | 7 + .../src/system/queries/path_elements.rs | 2 + .../src/errors/consensus/consensus_error.rs | 2 + .../state_transition/transition_types.rs | 1 + .../state_transition_factory.rs | 7 + .../base/state_transition.rs | 36 ++ .../src/state_transitions/proof_result.rs | 7 + 28 files changed, 703 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4eee1486775..66f0ba61607 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,16 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array 0.14.7", +] + [[package]] name = "aes" version = "0.8.4" @@ -569,6 +579,17 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake2b_simd" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79834656f71332577234b50bfc009996f7449e0c056884e6a02492ded0ca2f3" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq 0.4.2", +] + [[package]] name = "blake3" version = "1.8.3" @@ -622,6 +643,17 @@ dependencies = [ "serde", ] +[[package]] +name = "bls12_381" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc6d6292be3a19e6379786dac800f551e5865a5bb51ebbe3064ab80433f403" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "blsful" version = "3.0.0" @@ -865,6 +897,30 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "check-features" version = "3.1.0-dev.1" @@ -943,6 +999,7 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", + "zeroize", ] [[package]] @@ -1122,6 +1179,15 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core2" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "239fa3ae9b63c2dc74bd3fa852d4792b8b305ae64eeede946265b6af62f1fff3" +dependencies = [ + "memchr", +] + [[package]] name = "core2" version = "0.4.0" @@ -1849,6 +1915,7 @@ dependencies = [ "dpp", "env_logger 0.11.9", "getrandom 0.2.17", + "grovedb-commitment-tree", "hex", "indexmap 2.13.0", "integer-encoding", @@ -2334,6 +2401,20 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fpe" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c4b37de5ae15812a764c958297cfc50f5c010438f60c6ce75d11b802abd404" +dependencies = [ + "cbc", + "cipher", + "libm", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "fraction" version = "0.15.3" @@ -2512,6 +2593,18 @@ dependencies = [ "wasip3", ] +[[package]] +name = "getset" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf0fc11e47561d47397154977bc219f4cf809b2974facc3ccb3b89e2436f912" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "glob" version = "0.3.3" @@ -2550,6 +2643,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", + "memuse", "rand 0.8.5", "rand_core 0.6.4", "rand_xorshift", @@ -2559,16 +2653,21 @@ dependencies = [ [[package]] name = "grovedb" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=33dfd48a1718160cb333fa95424be491785f1897#33dfd48a1718160cb333fa95424be491785f1897" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" dependencies = [ "axum 0.8.8", "bincode", "bincode_derive", "blake3", + "grovedb-bulk-append-tree", + "grovedb-commitment-tree", "grovedb-costs", + "grovedb-dense-fixed-sized-merkle-tree", "grovedb-element", "grovedb-merk", + "grovedb-merkle-mountain-range", "grovedb-path", + "grovedb-query", "grovedb-storage", "grovedb-version", "grovedb-visualize", @@ -2589,20 +2688,63 @@ dependencies = [ "zip-extensions", ] +[[package]] +name = "grovedb-bulk-append-tree" +version = "4.0.0" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" +dependencies = [ + "bincode", + "blake3", + "grovedb-costs", + "grovedb-dense-fixed-sized-merkle-tree", + "grovedb-merkle-mountain-range", + "grovedb-query", + "grovedb-storage", + "hex", + "thiserror 2.0.18", +] + +[[package]] +name = "grovedb-commitment-tree" +version = "4.0.0" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" +dependencies = [ + "blake3", + "grovedb-bulk-append-tree", + "grovedb-costs", + "grovedb-storage", + "incrementalmerkletree", + "orchard", + "thiserror 2.0.18", +] + [[package]] name = "grovedb-costs" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=33dfd48a1718160cb333fa95424be491785f1897#33dfd48a1718160cb333fa95424be491785f1897" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" dependencies = [ "integer-encoding", "intmap", "thiserror 2.0.18", ] +[[package]] +name = "grovedb-dense-fixed-sized-merkle-tree" +version = "4.0.0" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" +dependencies = [ + "bincode", + "blake3", + "grovedb-costs", + "grovedb-query", + "grovedb-storage", + "thiserror 2.0.18", +] + [[package]] name = "grovedb-element" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=33dfd48a1718160cb333fa95424be491785f1897#33dfd48a1718160cb333fa95424be491785f1897" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" dependencies = [ "bincode", "bincode_derive", @@ -2617,7 +2759,7 @@ dependencies = [ [[package]] name = "grovedb-epoch-based-storage-flags" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=33dfd48a1718160cb333fa95424be491785f1897#33dfd48a1718160cb333fa95424be491785f1897" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" dependencies = [ "grovedb-costs", "hex", @@ -2629,7 +2771,7 @@ dependencies = [ [[package]] name = "grovedb-merk" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=33dfd48a1718160cb333fa95424be491785f1897#33dfd48a1718160cb333fa95424be491785f1897" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" dependencies = [ "bincode", "bincode_derive", @@ -2640,6 +2782,7 @@ dependencies = [ "grovedb-costs", "grovedb-element", "grovedb-path", + "grovedb-query", "grovedb-storage", "grovedb-version", "grovedb-visualize", @@ -2651,18 +2794,45 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "grovedb-merkle-mountain-range" +version = "4.0.0" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" +dependencies = [ + "bincode", + "blake3", + "grovedb-costs", + "grovedb-storage", +] + [[package]] name = "grovedb-path" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=33dfd48a1718160cb333fa95424be491785f1897#33dfd48a1718160cb333fa95424be491785f1897" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" +dependencies = [ + "hex", +] + +[[package]] +name = "grovedb-query" +version = "4.0.0" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" dependencies = [ + "bincode", + "byteorder", + "ed", + "grovedb-costs", + "grovedb-storage", "hex", + "indexmap 2.13.0", + "integer-encoding", + "thiserror 2.0.18", ] [[package]] name = "grovedb-storage" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=33dfd48a1718160cb333fa95424be491785f1897#33dfd48a1718160cb333fa95424be491785f1897" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" dependencies = [ "blake3", "grovedb-costs", @@ -2681,7 +2851,7 @@ dependencies = [ [[package]] name = "grovedb-version" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=33dfd48a1718160cb333fa95424be491785f1897#33dfd48a1718160cb333fa95424be491785f1897" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" dependencies = [ "thiserror 2.0.18", "versioned-feature-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2690,7 +2860,7 @@ dependencies = [ [[package]] name = "grovedb-visualize" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=33dfd48a1718160cb333fa95424be491785f1897#33dfd48a1718160cb333fa95424be491785f1897" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" dependencies = [ "hex", "itertools 0.14.0", @@ -2699,7 +2869,7 @@ dependencies = [ [[package]] name = "grovedbg-types" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=33dfd48a1718160cb333fa95424be491785f1897#33dfd48a1718160cb333fa95424be491785f1897" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" dependencies = [ "serde", "serde_with 3.17.0", @@ -2735,6 +2905,61 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "halo2_gadgets" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45824ce0dd12e91ec0c68ebae2a7ed8ae19b70946624c849add59f1d1a62a143" +dependencies = [ + "arrayvec", + "bitvec", + "ff", + "group", + "halo2_poseidon", + "halo2_proofs", + "lazy_static", + "pasta_curves", + "rand 0.8.5", + "sinsemilla", + "subtle", + "uint", +] + +[[package]] +name = "halo2_legacy_pdqsort" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47716fe1ae67969c5e0b2ef826f32db8c3be72be325e1aa3c1951d06b5575ec5" + +[[package]] +name = "halo2_poseidon" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa3da60b81f02f9b33ebc6252d766f843291fb4d2247a07ae73d20b791fc56f" +dependencies = [ + "bitvec", + "ff", + "group", + "pasta_curves", +] + +[[package]] +name = "halo2_proofs" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05713f117155643ce10975e0bee44a274bcda2f4bb5ef29a999ad67c1fa8d4d3" +dependencies = [ + "blake2b_simd", + "ff", + "group", + "halo2_legacy_pdqsort", + "indexmap 1.9.3", + "maybe-rayon", + "pasta_curves", + "rand_core 0.6.4", + "tracing", +] + [[package]] name = "hash32" version = "0.3.1" @@ -3233,6 +3458,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "incrementalmerkletree" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30821f91f0fa8660edca547918dc59812893b497d07c1144f326f07fdd94aba9" +dependencies = [ + "either", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -3473,6 +3707,20 @@ dependencies = [ "uuid", ] +[[package]] +name = "jubjub" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8499f7a74008aafbecb2a2e608a3e13e4dd3e84df198b604451efe93f2de6e61" +dependencies = [ + "bitvec", + "bls12_381", + "ff", + "group", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "keccak" version = "0.1.6" @@ -3560,6 +3808,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin 0.9.8", +] [[package]] name = "lazycell" @@ -3725,12 +3976,28 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + [[package]] name = "memchr" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "memuse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d97bbf43eb4f088f8ca469930cde17fa036207c9a5e02ccc5107c4e8b17c964" + [[package]] name = "merlin" version = "3.0.0" @@ -3956,6 +4223,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "nonempty" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "549e471b99ccaf2f89101bec68f4d244457d5a95a9c3d0672e9564124397741d" + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -4142,6 +4415,12 @@ version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "openssl" version = "0.10.75" @@ -4186,6 +4465,41 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "orchard" +version = "0.12.0" +source = "git+https://github.com/dashpay/orchard.git?rev=41c8f7169f2683c99cf0e0c63e8d25ec12c47a79#41c8f7169f2683c99cf0e0c63e8d25ec12c47a79" +dependencies = [ + "aes", + "bitvec", + "blake2b_simd", + "core2 0.3.3", + "ff", + "fpe", + "getset", + "group", + "halo2_gadgets", + "halo2_poseidon", + "halo2_proofs", + "hex", + "incrementalmerkletree", + "lazy_static", + "memuse", + "nonempty", + "pasta_curves", + "rand 0.8.5", + "rand_core 0.6.4", + "reddsa", + "serde", + "sinsemilla", + "subtle", + "tracing", + "visibility", + "zcash_note_encryption", + "zcash_spec", + "zip32", +] + [[package]] name = "pairing" version = "0.23.0" @@ -4233,6 +4547,21 @@ dependencies = [ "regex", ] +[[package]] +name = "pasta_curves" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e57598f73cc7e1b2ac63c79c517b31a0877cd7c402cdcaa311b5208de7a095" +dependencies = [ + "blake2b_simd", + "ff", + "group", + "lazy_static", + "rand 0.8.5", + "static_assertions", + "subtle", +] + [[package]] name = "paste" version = "1.0.15" @@ -4495,6 +4824,17 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "portable-atomic" version = "1.13.1" @@ -4599,6 +4939,28 @@ dependencies = [ "toml_edit 0.23.10+spec-1.0.0", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -4977,6 +5339,24 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "reddsa" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78a5191930e84973293aa5f532b513404460cd2216c1cfb76d08748c15b40b02" +dependencies = [ + "blake2b_simd", + "byteorder", + "group", + "hex", + "jubjub", + "pasta_curves", + "rand_core 0.6.4", + "serde", + "thiserror 1.0.69", + "zeroize", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -5997,6 +6377,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "sinsemilla" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d268ae0ea06faafe1662e9967cd4f9022014f5eeb798e0c302c876df8b7af9c" +dependencies = [ + "group", + "pasta_curves", + "subtle", +] + [[package]] name = "siphasher" version = "1.0.2" @@ -6041,6 +6432,12 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "spin" version = "0.10.0" @@ -6072,6 +6469,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "std-shims" version = "0.1.5" @@ -6080,7 +6483,7 @@ checksum = "227c4f8561598188d0df96dbe749824576174bba278b5b6bb2eacff1066067d0" dependencies = [ "hashbrown 0.16.1", "rustversion", - "spin", + "spin 0.10.0", ] [[package]] @@ -7037,13 +7440,25 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "uint-zigzag" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abbf77aed65cb885a8ba07138c365879be3d9a93dce82bf6cc50feca9138ec15" dependencies = [ - "core2", + "core2 0.4.0", ] [[package]] @@ -7079,6 +7494,16 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "untrusted" version = "0.9.0" @@ -7203,6 +7628,17 @@ version = "0.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" +[[package]] +name = "visibility" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "vsss-rs" version = "5.1.0" @@ -8088,6 +8524,27 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zcash_note_encryption" +version = "0.4.1" +source = "git+https://github.com/dashpay/zcash_note_encryption?rev=9f7e93d#9f7e93d42cef839d02b9d75918117941d453f8cb" +dependencies = [ + "chacha20", + "chacha20poly1305", + "cipher", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "zcash_spec" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded3f58b93486aa79b85acba1001f5298f27a46489859934954d262533ee2915" +dependencies = [ + "blake2b_simd", +] + [[package]] name = "zerocopy" version = "0.8.39" @@ -8252,6 +8709,19 @@ dependencies = [ "zip 6.0.0", ] +[[package]] +name = "zip32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b64bf5186a8916f7a48f2a98ef599bf9c099e2458b36b819e393db1c0e768c4b" +dependencies = [ + "bech32 0.11.1", + "blake2b_simd", + "memuse", + "subtle", + "zcash_spec", +] + [[package]] name = "zlib-rs" version = "0.6.2" diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/address_balances_and_nonces.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/address_balances_and_nonces.rs index e614e861e1d..21ce47e8173 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/address_balances_and_nonces.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/address_balances_and_nonces.rs @@ -175,6 +175,13 @@ impl StateTransitionAddressBalancesAndNoncesValidation for StateTransition { | StateTransition::IdentityCreditTransfer(_) | StateTransition::MasternodeVote(_) | StateTransition::IdentityCreditTransferToAddresses(_) => false, + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } } } @@ -234,6 +241,13 @@ impl StateTransitionAddressBalancesAndNoncesValidation for StateTransition { | StateTransition::IdentityCreditTransferToAddresses(_) => { Ok(ConsensusValidationResult::new_with_data(BTreeMap::new())) } + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } } } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/address_witnesses.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/address_witnesses.rs index 8aede4de054..4ba9f70cdd1 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/address_witnesses.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/address_witnesses.rs @@ -81,6 +81,13 @@ impl StateTransitionAddressWitnessValidationV0 for StateTransition { | StateTransition::IdentityCreditTransferToAddresses(_) => { return Ok(SimpleConsensusValidationResult::new()); } + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } }; // Add operations to execution context for fee calculation @@ -191,6 +198,13 @@ impl StateTransitionHasAddressWitnessValidationV0 for StateTransition { | StateTransition::IdentityCreditTransfer(_) | StateTransition::MasternodeVote(_) | StateTransition::IdentityCreditTransferToAddresses(_) => false, + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } }; Ok(has_address_witness_validation) diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/addresses_minimum_balance.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/addresses_minimum_balance.rs index c76b79953d5..589c90bd428 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/addresses_minimum_balance.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/addresses_minimum_balance.rs @@ -72,6 +72,13 @@ impl StateTransitionAddressesMinimumBalanceValidationV0 for StateTransition { | StateTransition::MasternodeVote(_) => { return Ok(SimpleConsensusValidationResult::new()); } + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } }?; // Convert ConsensusValidationResult to SimpleConsensusValidationResult @@ -97,6 +104,13 @@ impl StateTransitionAddressesMinimumBalanceValidationV0 for StateTransition { | StateTransition::Batch(_) | StateTransition::MasternodeVote(_) | StateTransition::AddressFundingFromAssetLock(_) => false, + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } } } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/basic_structure.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/basic_structure.rs index b9d1c491e7a..0eefb307c9b 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/basic_structure.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/basic_structure.rs @@ -237,6 +237,13 @@ impl StateTransitionBasicStructureValidationV0 for StateTransition { })), } } + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } } } fn has_basic_structure_validation(&self, platform_version: &PlatformVersion) -> bool { @@ -274,6 +281,13 @@ impl StateTransitionBasicStructureValidationV0 for StateTransition { | StateTransition::AddressFundingFromAssetLock(_) | StateTransition::AddressCreditWithdrawal(_) => true, StateTransition::MasternodeVote(_) => false, + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } } } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_balance.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_balance.rs index e9bd2e6dd34..d9bab90f70b 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_balance.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_balance.rs @@ -79,6 +79,13 @@ impl StateTransitionIdentityBalanceValidationV0 for StateTransition { | StateTransition::AddressCreditWithdrawal(_) => { Ok(SimpleConsensusValidationResult::new()) } + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_based_signature.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_based_signature.rs index e7854d46439..289f087f664 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_based_signature.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_based_signature.rs @@ -131,6 +131,13 @@ impl StateTransitionIdentityBasedSignatureValidationV0 for StateTransition { | StateTransition::AddressFundsTransfer(_) | StateTransition::AddressFundingFromAssetLock(_) | StateTransition::AddressCreditWithdrawal(_) => Ok(ConsensusValidationResult::new()), + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } } } @@ -176,6 +183,13 @@ impl StateTransitionIdentityBasedSignatureValidationV0 for StateTransition { | StateTransition::MasternodeVote(_) | StateTransition::IdentityCreditTransferToAddresses(_) | StateTransition::IdentityTopUpFromAddresses(_) => true, + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } } } @@ -197,6 +211,13 @@ impl StateTransitionIdentityBasedSignatureValidationV0 for StateTransition { | StateTransition::IdentityCreditTransfer(_) | StateTransition::MasternodeVote(_) | StateTransition::IdentityCreditTransferToAddresses(_) => true, + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } } } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_nonces.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_nonces.rs index d54e789757e..2803fda3947 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_nonces.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_nonces.rs @@ -115,6 +115,13 @@ impl StateTransitionIdentityNonceValidationV0 for StateTransition { | StateTransition::AddressFundsTransfer(_) | StateTransition::IdentityCreate(_) | StateTransition::IdentityTopUp(_) => Ok(SimpleConsensusValidationResult::new()), + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } } } } @@ -161,6 +168,13 @@ impl StateTransitionHasIdentityNonceValidationV0 for StateTransition { | StateTransition::AddressFundsTransfer(_) | StateTransition::AddressFundingFromAssetLock(_) | StateTransition::AddressCreditWithdrawal(_) => false, + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } }; Ok(has_nonce_validation) diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/is_allowed.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/is_allowed.rs index 935b725bca3..22af15ae8f0 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/is_allowed.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/is_allowed.rs @@ -38,6 +38,13 @@ impl StateTransitionIsAllowedValidationV0 for StateTransition { | StateTransition::IdentityUpdate(_) | StateTransition::IdentityCreditTransfer(_) | StateTransition::MasternodeVote(_) => Ok(false), + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/state.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/state.rs index f9d176acc19..5c426029c23 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/state.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/state.rs @@ -185,6 +185,13 @@ impl StateTransitionStateValidation for StateTransition { "address credit withdrawal should not have state validation", ))) } + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } } } @@ -205,6 +212,13 @@ impl StateTransitionStateValidation for StateTransition { | StateTransition::IdentityCreditWithdrawal(_) | StateTransition::AddressCreditWithdrawal(_) | StateTransition::IdentityCreditTransferToAddresses(_) => false, + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } } } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/transformer/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/transformer/mod.rs index 3ea8b849437..f8b8d7e0e75 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/transformer/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/transformer/mod.rs @@ -229,6 +229,13 @@ impl StateTransitionActionTransformer for StateTransition { remaining_address_input_balances.clone(), ) } + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } } } } diff --git a/packages/rs-drive/Cargo.toml b/packages/rs-drive/Cargo.toml index 2fd0676d080..42f2c41c5aa 100644 --- a/packages/rs-drive/Cargo.toml +++ b/packages/rs-drive/Cargo.toml @@ -52,12 +52,12 @@ enum-map = { version = "2.0.3", optional = true } intmap = { version = "3.0.1", features = ["serde"], optional = true } chrono = { version = "0.4.35", optional = true } itertools = { version = "0.13", optional = true } -grovedb = { git = "https://github.com/dashpay/grovedb", rev = "33dfd48a1718160cb333fa95424be491785f1897", optional = true, default-features = false } -grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "33dfd48a1718160cb333fa95424be491785f1897", optional = true } -grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "33dfd48a1718160cb333fa95424be491785f1897" } -grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "33dfd48a1718160cb333fa95424be491785f1897", optional = true } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "33dfd48a1718160cb333fa95424be491785f1897" } -grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "33dfd48a1718160cb333fa95424be491785f1897" } +grovedb = { git = "https://github.com/dashpay/grovedb", rev = "7ecb8465fad750c7cddd5332adb6f97fcceb498b", optional = true, default-features = false } +grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "7ecb8465fad750c7cddd5332adb6f97fcceb498b", optional = true } +grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "7ecb8465fad750c7cddd5332adb6f97fcceb498b" } +grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "7ecb8465fad750c7cddd5332adb6f97fcceb498b", optional = true } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "7ecb8465fad750c7cddd5332adb6f97fcceb498b" } +grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "7ecb8465fad750c7cddd5332adb6f97fcceb498b" } [dev-dependencies] criterion = "0.5" diff --git a/packages/rs-drive/src/drive/identity/contract_info/keys/add_potential_contract_info_for_contract_bounded_key/v0/mod.rs b/packages/rs-drive/src/drive/identity/contract_info/keys/add_potential_contract_info_for_contract_bounded_key/v0/mod.rs index 55fc9ba8013..a8b2a949f83 100644 --- a/packages/rs-drive/src/drive/identity/contract_info/keys/add_potential_contract_info_for_contract_bounded_key/v0/mod.rs +++ b/packages/rs-drive/src/drive/identity/contract_info/keys/add_potential_contract_info_for_contract_bounded_key/v0/mod.rs @@ -142,6 +142,7 @@ impl Drive { storage_cost: Default::default(), storage_loaded_bytes: 100, hash_node_calls: 0, + sinsemilla_hash_calls: 0, }, )); None diff --git a/packages/rs-drive/src/drive/identity/contract_info/keys/refresh_potential_contract_info_key_references/v0/mod.rs b/packages/rs-drive/src/drive/identity/contract_info/keys/refresh_potential_contract_info_key_references/v0/mod.rs index 25f20f62385..eddd0c9f029 100644 --- a/packages/rs-drive/src/drive/identity/contract_info/keys/refresh_potential_contract_info_key_references/v0/mod.rs +++ b/packages/rs-drive/src/drive/identity/contract_info/keys/refresh_potential_contract_info_key_references/v0/mod.rs @@ -113,6 +113,7 @@ impl Drive { storage_cost: Default::default(), storage_loaded_bytes: 100, hash_node_calls: 0, + sinsemilla_hash_calls: 0, }, )); None diff --git a/packages/rs-drive/src/fees/op.rs b/packages/rs-drive/src/fees/op.rs index 9364d8f473b..7406591e031 100644 --- a/packages/rs-drive/src/fees/op.rs +++ b/packages/rs-drive/src/fees/op.rs @@ -579,6 +579,7 @@ impl LowLevelDriveOperationTreeTypeConverter for TreeType { TreeType::ProvableCountSumTree => { Element::empty_provable_count_sum_tree_with_flags(element_flags) } + _ => todo!("new tree types not yet implemented"), }; LowLevelDriveOperation::insert_for_known_path_key_element(path, key, element) @@ -599,6 +600,7 @@ impl DriveCost for OperationCost { storage_cost, storage_loaded_bytes, hash_node_calls, + sinsemilla_hash_calls: _, } = self; let epoch_cost_for_processing_credit_per_byte = fee_version.storage.storage_processing_credit_per_byte; diff --git a/packages/rs-drive/src/prove/prove_state_transition/v0/mod.rs b/packages/rs-drive/src/prove/prove_state_transition/v0/mod.rs index 6f3837e0201..97d4647c957 100644 --- a/packages/rs-drive/src/prove/prove_state_transition/v0/mod.rs +++ b/packages/rs-drive/src/prove/prove_state_transition/v0/mod.rs @@ -301,6 +301,13 @@ impl Drive { .chain(st.output().into_iter().map(|(address, _)| address)); Drive::balances_for_clear_addresses_query(addresses_to_check) } + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented in prove_state_transition") + } }; let proof = self.grove_get_proved_path_query( diff --git a/packages/rs-drive/src/util/batch/grovedb_op_batch/mod.rs b/packages/rs-drive/src/util/batch/grovedb_op_batch/mod.rs index faf068d9ac7..52aec081552 100644 --- a/packages/rs-drive/src/util/batch/grovedb_op_batch/mod.rs +++ b/packages/rs-drive/src/util/batch/grovedb_op_batch/mod.rs @@ -332,7 +332,11 @@ impl fmt::Display for GroveDbOpBatch { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for op in &self.operations { let (path_string, known_path) = readable_path(&op.path); - let (key_string, _) = readable_key_info(known_path, &op.key); + let (key_string, _) = if let Some(ref key) = op.key { + readable_key_info(known_path, key) + } else { + ("None".to_string(), None) + }; writeln!(f, "{{")?; writeln!(f, " Path: {}", path_string)?; writeln!(f, " Key: {}", key_string)?; @@ -622,7 +626,7 @@ impl GroveDbOpBatchV0Methods for GroveDbOpBatch { ); self.operations.iter().find_map(|op| { - if op.path == path && op.key == KeyInfo::KnownKey(key.to_vec()) { + if op.path == path && op.key == Some(KeyInfo::KnownKey(key.to_vec())) { Some(&op.op) } else { None @@ -654,7 +658,7 @@ impl GroveDbOpBatchV0Methods for GroveDbOpBatch { if let Some(index) = self .operations .iter() - .position(|op| op.path == path && op.key == KeyInfo::KnownKey(key.to_vec())) + .position(|op| op.path == path && op.key == Some(KeyInfo::KnownKey(key.to_vec()))) { Some(self.operations.remove(index).op) } else { @@ -684,7 +688,7 @@ impl GroveDbOpBatchV0Methods for GroveDbOpBatch { if let Some(index) = self .operations .iter() - .position(|op| op.path == path && op.key == KeyInfo::KnownKey(key.to_vec())) + .position(|op| op.path == path && op.key == Some(KeyInfo::KnownKey(key.to_vec()))) { let op = &self.operations[index].op; let op = if matches!( diff --git a/packages/rs-drive/src/util/grove_operations/batch_insert_empty_tree_if_not_exists/v0/mod.rs b/packages/rs-drive/src/util/grove_operations/batch_insert_empty_tree_if_not_exists/v0/mod.rs index 2b3e5750f39..f982014d9cd 100644 --- a/packages/rs-drive/src/util/grove_operations/batch_insert_empty_tree_if_not_exists/v0/mod.rs +++ b/packages/rs-drive/src/util/grove_operations/batch_insert_empty_tree_if_not_exists/v0/mod.rs @@ -11,6 +11,7 @@ use crate::util::object_size_info::PathKeyInfo::{ use crate::util::storage_flags::StorageFlags; use dpp::version::drive_versions::DriveVersion; use grovedb::batch::GroveOp; +use grovedb::batch::key_info::KeyInfo; use grovedb::{TransactionArg, TreeType}; impl Drive { @@ -49,7 +50,7 @@ impl Drive { found = true; break; } else if let GroveOperation(grove_op) = previous_drive_operation { - if grove_op.key == key + if grove_op.key == Some(KeyInfo::KnownKey(key.to_vec())) && grove_op.path == path && matches!(grove_op.op, GroveOp::DeleteTree(_)) { @@ -116,7 +117,7 @@ impl Drive { found = true; break; } else if let GroveOperation(grove_op) = previous_drive_operation { - if grove_op.key == key + if grove_op.key == Some(KeyInfo::KnownKey(key.to_vec())) && grove_op.path == path && matches!(grove_op.op, GroveOp::DeleteTree(_)) { @@ -181,7 +182,7 @@ impl Drive { found = true; break; } else if let GroveOperation(grove_op) = previous_drive_operation { - if grove_op.key == key + if grove_op.key == Some(KeyInfo::KnownKey(key.to_vec())) && grove_op.path == path && matches!(grove_op.op, GroveOp::DeleteTree(_)) { @@ -246,7 +247,7 @@ impl Drive { found = true; break; } else if let GroveOperation(grove_op) = previous_drive_operation { - if grove_op.key == key + if grove_op.key == Some(KeyInfo::KnownKey(key.to_vec())) && grove_op.path == path && matches!(grove_op.op, GroveOp::DeleteTree(_)) { diff --git a/packages/rs-drive/src/util/grove_operations/grove_insert_empty_tree/v0/mod.rs b/packages/rs-drive/src/util/grove_operations/grove_insert_empty_tree/v0/mod.rs index 2cdfa09e3f3..6abf5bdef4b 100644 --- a/packages/rs-drive/src/util/grove_operations/grove_insert_empty_tree/v0/mod.rs +++ b/packages/rs-drive/src/util/grove_operations/grove_insert_empty_tree/v0/mod.rs @@ -28,6 +28,7 @@ impl Drive { TreeType::CountSumTree => Element::empty_count_sum_tree(), TreeType::ProvableCountTree => Element::empty_provable_count_tree(), TreeType::ProvableCountSumTree => Element::empty_provable_count_sum_tree(), + _ => todo!("new tree types not yet implemented"), }; let cost_context = self.grove.insert( path, diff --git a/packages/rs-drive/src/util/operations/apply_partial_batch_grovedb_operations/v0/mod.rs b/packages/rs-drive/src/util/operations/apply_partial_batch_grovedb_operations/v0/mod.rs index ff260a535bb..87d7382bd7a 100644 --- a/packages/rs-drive/src/util/operations/apply_partial_batch_grovedb_operations/v0/mod.rs +++ b/packages/rs-drive/src/util/operations/apply_partial_batch_grovedb_operations/v0/mod.rs @@ -52,6 +52,7 @@ impl Drive { }, storage_loaded_bytes: 1, hash_node_calls: 1, + sinsemilla_hash_calls: 0, }, &None, )?; diff --git a/packages/rs-drive/src/verify/address_funds/verify_compacted_address_balance_changes/v0/mod.rs b/packages/rs-drive/src/verify/address_funds/verify_compacted_address_balance_changes/v0/mod.rs index 57a3222a97d..fef26e40c80 100644 --- a/packages/rs-drive/src/verify/address_funds/verify_compacted_address_balance_changes/v0/mod.rs +++ b/packages/rs-drive/src/verify/address_funds/verify_compacted_address_balance_changes/v0/mod.rs @@ -77,6 +77,7 @@ impl Drive { // Path: SavedBlockTransactions ('$' = 0x24) -> CompactedAddressBalances ('c' = 0x63) let root_layer = match &grovedb_proof { GroveDBProof::V0(v0) => &v0.root_layer, + _ => todo!("GroveDBProof::V1 not yet implemented"), }; let saved_block_key = vec![RootTree::SavedBlockTransactions as u8]; diff --git a/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs b/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs index e04c54f25ea..4f238162684 100644 --- a/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs +++ b/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs @@ -1115,6 +1115,13 @@ impl Drive { Ok((root_hash, VerifiedAddressInfos(balances))) } + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented in verify_state_transition_was_executed_with_proof") + } } } } diff --git a/packages/rs-sdk-ffi/src/system/queries/path_elements.rs b/packages/rs-sdk-ffi/src/system/queries/path_elements.rs index 47e1399c210..a8c6d03d801 100644 --- a/packages/rs-sdk-ffi/src/system/queries/path_elements.rs +++ b/packages/rs-sdk-ffi/src/system/queries/path_elements.rs @@ -156,6 +156,7 @@ fn get_path_elements( Element::ProvableCountSumTree(_, count, sum, _) => { format!("provable_count_sum_tree:{}:{}", count, sum) } + _ => format!("{:?}", element), }; format!( @@ -176,6 +177,7 @@ fn get_path_elements( Element::ProvableCountSumTree(_, _, _, _) => { "provable_count_sum_tree" } + _ => "unknown", } ) }) diff --git a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs index 429a94b1a4f..bf7c033473b 100644 --- a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs +++ b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs @@ -443,6 +443,7 @@ pub fn from_state_error(state_error: &StateError) -> JsValue { StateError::AddressInvalidNonceError(e) => { generic_consensus_error!(AddressInvalidNonceError, e).into() } + _ => JsError::new(&format!("unsupported state error: {:?}", state_error)).into(), } } @@ -923,6 +924,7 @@ fn from_basic_error(basic_error: &BasicError) -> JsValue { BasicError::WithdrawalBelowMinAmountError(e) => { generic_consensus_error!(WithdrawalBelowMinAmountError, e).into() } + _ => JsError::new(&format!("unsupported basic error: {:?}", basic_error)).into(), } } diff --git a/packages/wasm-dpp/src/identity/state_transition/transition_types.rs b/packages/wasm-dpp/src/identity/state_transition/transition_types.rs index 37fe3d2626f..29d2274dffe 100644 --- a/packages/wasm-dpp/src/identity/state_transition/transition_types.rs +++ b/packages/wasm-dpp/src/identity/state_transition/transition_types.rs @@ -55,6 +55,7 @@ impl From for StateTransitionTypeWasm { StateTransitionType::AddressCreditWithdrawal => { StateTransitionTypeWasm::AddressCreditWithdrawal } + _ => todo!("shielded state transition types not yet implemented in wasm"), } } } diff --git a/packages/wasm-dpp/src/state_transition/state_transition_factory.rs b/packages/wasm-dpp/src/state_transition/state_transition_factory.rs index c140f0e7ea6..2b929d17a15 100644 --- a/packages/wasm-dpp/src/state_transition/state_transition_factory.rs +++ b/packages/wasm-dpp/src/state_transition/state_transition_factory.rs @@ -79,6 +79,13 @@ impl StateTransitionFactoryWasm { StateTransition::AddressCreditWithdrawal(st) => { serde_wasm_bindgen::to_value(&st).map_err(|e| JsValue::from(e.to_string())) } + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented in state_transition_factory") + } }, Err(dpp::ProtocolError::StateTransitionError(e)) => match e { StateTransitionError::InvalidStateTransitionError { diff --git a/packages/wasm-dpp2/src/state_transitions/base/state_transition.rs b/packages/wasm-dpp2/src/state_transitions/base/state_transition.rs index 2cd140ac40c..ac29a07750d 100644 --- a/packages/wasm-dpp2/src/state_transitions/base/state_transition.rs +++ b/packages/wasm-dpp2/src/state_transitions/base/state_transition.rs @@ -308,6 +308,11 @@ impl StateTransitionWasm { AddressFundsTransfer(_) => 12, AddressFundingFromAssetLock(_) => 13, AddressCreditWithdrawal(_) => 14, + Shield(_) => 15, + ShieldedTransfer(_) => 16, + Unshield(_) => 17, + ShieldFromAssetLock(_) => 18, + ShieldedWithdrawal(_) => 19, } } @@ -392,6 +397,11 @@ impl StateTransitionWasm { | AddressFundsTransfer(_) | AddressFundingFromAssetLock(_) | AddressCreditWithdrawal(_) => None, + Shield(_) + | ShieldedTransfer(_) + | Unshield(_) + | ShieldFromAssetLock(_) + | ShieldedWithdrawal(_) => todo!("shielded transitions not yet implemented"), } } @@ -414,6 +424,11 @@ impl StateTransitionWasm { AddressFundsTransfer(_) | AddressFundingFromAssetLock(_) | AddressCreditWithdrawal(_) => None, + Shield(_) + | ShieldedTransfer(_) + | Unshield(_) + | ShieldFromAssetLock(_) + | ShieldedWithdrawal(_) => todo!("shielded transitions not yet implemented"), } } @@ -551,6 +566,13 @@ impl StateTransitionWasm { "Cannot set owner for address funds transfer transition", )); } + Shield(_) + | ShieldedTransfer(_) + | Unshield(_) + | ShieldFromAssetLock(_) + | ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } }; Ok(()) @@ -619,6 +641,13 @@ impl StateTransitionWasm { "Cannot set identity contract nonce for address-related transition types", )); } + Shield(_) + | ShieldedTransfer(_) + | Unshield(_) + | ShieldFromAssetLock(_) + | ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } }; Ok(()) @@ -707,6 +736,13 @@ impl StateTransitionWasm { "Cannot set identity nonce for address-related transition types", )); } + Shield(_) + | ShieldedTransfer(_) + | Unshield(_) + | ShieldFromAssetLock(_) + | ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } }; Ok(()) diff --git a/packages/wasm-dpp2/src/state_transitions/proof_result.rs b/packages/wasm-dpp2/src/state_transitions/proof_result.rs index 619328fbd8b..3ee57bb9385 100644 --- a/packages/wasm-dpp2/src/state_transitions/proof_result.rs +++ b/packages/wasm-dpp2/src/state_transitions/proof_result.rs @@ -894,6 +894,13 @@ pub fn convert_proof_result( } .into() } + + StateTransitionProofResult::VerifiedAssetLockConsumed(_) + | StateTransitionProofResult::VerifiedShieldedNullifiers(_) + | StateTransitionProofResult::VerifiedShieldedNullifiersWithAddressInfos(_, _) + | StateTransitionProofResult::VerifiedShieldedNullifiersWithWithdrawalDocument(_, _) => { + todo!("shielded proof results not yet implemented in wasm") + } }; Ok(js_value.into()) From e7eb9173f3db27286a9bfe856293abfd788e0c32 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 4 Mar 2026 19:13:33 +0700 Subject: [PATCH 03/30] refactor: address PR review feedback on shielded DPP types - OrchardAddress: wrap PaymentAddress directly instead of raw bytes - serialize_authorized_bundle: return SerializedBundle struct instead of tuple - Add From<&OrchardAddress> for PaymentAddress (infallible since wrapper) - Fix version comments in v7.rs (add reason for create_initial_state_structure, remove stale //changed comments) - Add comment explaining drive change in v12.rs - List new Element variants in rs-sdk-ffi path_elements.rs (CommitmentTree, MmrTree, BulkAppendTree, DenseAppendOnlyFixedSizeTree) - Add shielded consensus errors properly in wasm-dpp: - State: InvalidAnchor, NullifierAlreadySpent, InvalidShieldedProof, InsufficientPoolNotes, InsufficientShieldedFee - Basic: ShieldedNoActions, ShieldedTooManyActions, ShieldedEmptyProof, ShieldedZeroAnchor, ShieldedInvalidValueBalance, UnshieldAmountZero, UnshieldValueBalanceBelowAmount Co-Authored-By: Claude Opus 4.6 --- .../src/address_funds/platform_address.rs | 155 +++++++----------- packages/rs-dpp/src/shielded/builder.rs | 131 ++++++++------- .../src/version/drive_versions/v7.rs | 8 +- .../rs-platform-version/src/version/v12.rs | 2 +- .../src/system/queries/path_elements.rs | 10 +- .../src/errors/consensus/consensus_error.rs | 43 ++++- 6 files changed, 183 insertions(+), 166 deletions(-) diff --git a/packages/rs-dpp/src/address_funds/platform_address.rs b/packages/rs-dpp/src/address_funds/platform_address.rs index 5121f016e05..5e4987c3be7 100644 --- a/packages/rs-dpp/src/address_funds/platform_address.rs +++ b/packages/rs-dpp/src/address_funds/platform_address.rs @@ -583,19 +583,14 @@ pub const ORCHARD_ADDRESS_SIZE: usize = ORCHARD_DIVERSIFIER_SIZE + ORCHARD_PKD_S /// The raw Orchard address format matches Zcash Orchard (43 bytes), but the /// string encoding is Dash-specific (no F4Jumble, no Unified Address wrapper). /// -/// Use [`From`] to convert from the `orchard` crate's native type, -/// or [`to_payment_address()`](OrchardAddress::to_payment_address) to convert back -/// (with pk_d validation). +/// Wraps `grovedb_commitment_tree::PaymentAddress`. Use [`From`] +/// to convert from the orchard crate's native type, or [`inner()`](OrchardAddress::inner) +/// / [`into_inner()`](OrchardAddress::into_inner) to access the wrapped address. /// /// Requires the `shielded-bundle-building` feature. #[cfg(feature = "shielded-bundle-building")] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct OrchardAddress { - /// 11-byte diversifier derived from the FullViewingKey with an index. - diversifier: [u8; ORCHARD_DIVERSIFIER_SIZE], - /// 32-byte diversified transmission key (point on the Pallas curve). - pk_d: [u8; ORCHARD_PKD_SIZE], -} +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct OrchardAddress(grovedb_commitment_tree::PaymentAddress); #[cfg(feature = "shielded-bundle-building")] impl OrchardAddress { @@ -603,43 +598,37 @@ impl OrchardAddress { /// Produces 'z' as the first bech32 character. pub const ORCHARD_TYPE: u8 = 0x10; - /// Creates an OrchardAddress from its raw components. - pub fn from_parts( - diversifier: [u8; ORCHARD_DIVERSIFIER_SIZE], - pk_d: [u8; ORCHARD_PKD_SIZE], - ) -> Self { - Self { diversifier, pk_d } + /// Returns the inner [`PaymentAddress`](grovedb_commitment_tree::PaymentAddress). + pub fn inner(&self) -> &grovedb_commitment_tree::PaymentAddress { + &self.0 + } + + /// Consumes the wrapper and returns the inner `PaymentAddress`. + pub fn into_inner(self) -> grovedb_commitment_tree::PaymentAddress { + self.0 } /// Creates an OrchardAddress from a 43-byte raw address. /// /// The first 11 bytes are the diversifier, the next 32 are pk_d. - /// No validation is performed on pk_d; use [`From`] - /// for a pre-validated address. - pub fn from_raw_bytes(bytes: &[u8; ORCHARD_ADDRESS_SIZE]) -> Self { - let mut diversifier = [0u8; ORCHARD_DIVERSIFIER_SIZE]; - let mut pk_d = [0u8; ORCHARD_PKD_SIZE]; - diversifier.copy_from_slice(&bytes[..ORCHARD_DIVERSIFIER_SIZE]); - pk_d.copy_from_slice(&bytes[ORCHARD_DIVERSIFIER_SIZE..]); - Self { diversifier, pk_d } + /// Returns an error if `pk_d` is not a valid Pallas curve point. + pub fn from_raw_bytes( + bytes: &[u8; ORCHARD_ADDRESS_SIZE], + ) -> Result { + let addr = Option::from( + grovedb_commitment_tree::PaymentAddress::from_raw_address_bytes(bytes), + ) + .ok_or_else(|| { + ProtocolError::DecodingError( + "OrchardAddress pk_d is not a valid Pallas curve point".to_string(), + ) + })?; + Ok(Self(addr)) } /// Returns the raw 43-byte address (diversifier || pk_d). pub fn to_raw_bytes(&self) -> [u8; ORCHARD_ADDRESS_SIZE] { - let mut bytes = [0u8; ORCHARD_ADDRESS_SIZE]; - bytes[..ORCHARD_DIVERSIFIER_SIZE].copy_from_slice(&self.diversifier); - bytes[ORCHARD_DIVERSIFIER_SIZE..].copy_from_slice(&self.pk_d); - bytes - } - - /// Returns the 11-byte diversifier. - pub fn diversifier(&self) -> &[u8; ORCHARD_DIVERSIFIER_SIZE] { - &self.diversifier - } - - /// Returns the 32-byte diversified transmission key. - pub fn pk_d(&self) -> &[u8; ORCHARD_PKD_SIZE] { - &self.pk_d + self.0.to_raw_address_bytes() } /// Encodes the OrchardAddress as a bech32m string for the specified network. @@ -648,34 +637,18 @@ impl OrchardAddress { /// - Data: type_byte (0x10) || diversifier (11 bytes) || pk_d (32 bytes) /// - Total payload: 44 bytes /// - Checksum: bech32m (BIP-350) - /// - /// # Example - /// ```ignore - /// let address = OrchardAddress::from_raw_bytes(&raw_bytes); - /// let encoded = address.to_bech32m_string(Network::Dash); - /// // Returns something like "dash1z..." - /// ``` pub fn to_bech32m_string(&self, network: Network) -> String { let hrp_str = PlatformAddress::hrp_for_network(network); let hrp = Hrp::parse(hrp_str).expect("HRP is valid"); + let raw = self.to_raw_bytes(); let mut payload = Vec::with_capacity(1 + ORCHARD_ADDRESS_SIZE); payload.push(Self::ORCHARD_TYPE); - payload.extend_from_slice(&self.diversifier); - payload.extend_from_slice(&self.pk_d); + payload.extend_from_slice(&raw); bech32::encode::(hrp, &payload).expect("encoding should succeed") } - /// Converts this address to an Orchard [`PaymentAddress`](grovedb_commitment_tree::PaymentAddress). - /// - /// Returns an error if `pk_d` is not a valid Pallas curve point. - pub fn to_payment_address( - &self, - ) -> Result { - crate::shielded::builder::orchard_address_to_payment_address(self) - } - /// Decodes a bech32m-encoded Orchard address string. /// /// # Returns @@ -714,22 +687,17 @@ impl OrchardAddress { ))); } - let mut diversifier = [0u8; ORCHARD_DIVERSIFIER_SIZE]; - let mut pk_d = [0u8; ORCHARD_PKD_SIZE]; - diversifier.copy_from_slice(&data[1..1 + ORCHARD_DIVERSIFIER_SIZE]); - pk_d.copy_from_slice(&data[1 + ORCHARD_DIVERSIFIER_SIZE..]); - - Ok((Self { diversifier, pk_d }, network)) + let mut raw = [0u8; ORCHARD_ADDRESS_SIZE]; + raw.copy_from_slice(&data[1..]); + Self::from_raw_bytes(&raw).map(|addr| (addr, network)) } } /// Infallible conversion from the orchard crate's `PaymentAddress` to `OrchardAddress`. -/// -/// Extracts the raw 43 bytes (diversifier || pk_d) from the validated address. #[cfg(feature = "shielded-bundle-building")] impl From for OrchardAddress { fn from(addr: grovedb_commitment_tree::PaymentAddress) -> Self { - Self::from_raw_bytes(&addr.to_raw_address_bytes()) + Self(addr) } } @@ -737,18 +705,19 @@ impl From for OrchardAddress { #[cfg(feature = "shielded-bundle-building")] impl From<&grovedb_commitment_tree::PaymentAddress> for OrchardAddress { fn from(addr: &grovedb_commitment_tree::PaymentAddress) -> Self { - Self::from_raw_bytes(&addr.to_raw_address_bytes()) + Self(*addr) } } #[cfg(feature = "shielded-bundle-building")] impl std::fmt::Display for OrchardAddress { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let raw = self.to_raw_bytes(); write!( f, "Orchard(d={}, pk_d={})", - hex::encode(self.diversifier), - hex::encode(self.pk_d) + hex::encode(&raw[..ORCHARD_DIVERSIFIER_SIZE]), + hex::encode(&raw[ORCHARD_DIVERSIFIER_SIZE..]) ) } } @@ -1517,34 +1486,29 @@ mod tests { // ======================== #[cfg(feature = "shielded-bundle-building")] - #[test] - fn test_orchard_address_from_parts_roundtrip() { - let diversifier = [ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, - ]; - let pk_d = [0xAB; 32]; - let address = OrchardAddress::from_parts(diversifier, pk_d); - - assert_eq!(address.diversifier(), &diversifier); - assert_eq!(address.pk_d(), &pk_d); + fn test_orchard_address() -> OrchardAddress { + use grovedb_commitment_tree::{FullViewingKey, Scope, SpendingKey}; + let sk = SpendingKey::from_bytes([42u8; 32]).unwrap(); + let fvk = FullViewingKey::from(&sk); + let payment_address = fvk.address_at(0u32, Scope::External); + OrchardAddress::from(payment_address) + } + #[cfg(feature = "shielded-bundle-building")] + #[test] + fn test_orchard_address_raw_bytes_roundtrip() { + let address = test_orchard_address(); let raw = address.to_raw_bytes(); assert_eq!(raw.len(), 43); - assert_eq!(&raw[..11], &diversifier); - assert_eq!(&raw[11..], &pk_d[..]); - let recovered = OrchardAddress::from_raw_bytes(&raw); + let recovered = OrchardAddress::from_raw_bytes(&raw).unwrap(); assert_eq!(recovered, address); } #[cfg(feature = "shielded-bundle-building")] #[test] fn test_orchard_bech32m_mainnet_roundtrip() { - let diversifier = [ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, - ]; - let pk_d = [0xAB; 32]; - let address = OrchardAddress::from_parts(diversifier, pk_d); + let address = test_orchard_address(); let encoded = address.to_bech32m_string(Network::Dash); assert!( @@ -1562,9 +1526,7 @@ mod tests { #[cfg(feature = "shielded-bundle-building")] #[test] fn test_orchard_bech32m_testnet_roundtrip() { - let diversifier = [0xFF; 11]; - let pk_d = [0x42; 32]; - let address = OrchardAddress::from_parts(diversifier, pk_d); + let address = test_orchard_address(); let encoded = address.to_bech32m_string(Network::Testnet); assert!( @@ -1616,10 +1578,9 @@ mod tests { #[cfg(feature = "shielded-bundle-building")] #[test] fn test_orchard_and_platform_addresses_are_distinguishable() { - // Verify that the type bytes produce distinct prefixes let p2pkh = PlatformAddress::P2pkh([0xAB; 20]); let p2sh = PlatformAddress::P2sh([0xAB; 20]); - let orchard = OrchardAddress::from_parts([0xAB; 11], [0xAB; 32]); + let orchard = test_orchard_address(); let p2pkh_enc = p2pkh.to_bech32m_string(Network::Dash); let p2sh_enc = p2sh.to_bech32m_string(Network::Dash); @@ -1641,17 +1602,17 @@ mod tests { #[cfg(feature = "shielded-bundle-building")] #[test] - fn test_orchard_address_all_zeros() { - let address = OrchardAddress::from_parts([0u8; 11], [0u8; 32]); - let encoded = address.to_bech32m_string(Network::Dash); - let (decoded, _) = OrchardAddress::from_bech32m_string(&encoded).unwrap(); - assert_eq!(decoded, address); + fn test_orchard_address_from_raw_bytes_invalid_pk_d() { + // All zeros for pk_d is not a valid Pallas curve point + let mut raw = [0u8; 43]; + raw[0] = 0x01; // non-zero diversifier + assert!(OrchardAddress::from_raw_bytes(&raw).is_err()); } #[cfg(feature = "shielded-bundle-building")] #[test] fn test_orchard_address_display() { - let address = OrchardAddress::from_parts([0x01; 11], [0x02; 32]); + let address = test_orchard_address(); let display = format!("{}", address); assert!(display.starts_with("Orchard(d=")); assert!(display.contains("pk_d=")); diff --git a/packages/rs-dpp/src/shielded/builder.rs b/packages/rs-dpp/src/shielded/builder.rs index 878ee41c015..da4c43c5db0 100644 --- a/packages/rs-dpp/src/shielded/builder.rs +++ b/packages/rs-dpp/src/shielded/builder.rs @@ -67,27 +67,34 @@ pub struct SpendableNote { pub merkle_path: MerklePath, } -/// Converts an [`OrchardAddress`] to an Orchard [`PaymentAddress`]. -/// -/// Returns an error if `pk_d` is not a valid Pallas curve point. -pub fn orchard_address_to_payment_address( - address: &OrchardAddress, -) -> Result { - let raw = address.to_raw_bytes(); - Option::from(PaymentAddress::from_raw_address_bytes(&raw)).ok_or_else(|| { - ProtocolError::DecodingError( - "OrchardAddress pk_d is not a valid Pallas curve point".to_string(), - ) - }) +/// The serialized fields extracted from an authorized Orchard bundle, +/// ready for use by state transition constructors. +pub struct SerializedBundle { + /// Serialized Orchard actions (spends + outputs). + pub actions: Vec, + /// Bundle flags byte. + pub flags: u8, + /// Net value balance (positive = value leaving the shielded pool). + pub value_balance: i64, + /// Merkle tree anchor (32 bytes). + pub anchor: [u8; 32], + /// Halo 2 proof bytes. + pub proof: Vec, + /// Binding signature (64 bytes). + pub binding_signature: [u8; 64], +} + +impl From<&OrchardAddress> for PaymentAddress { + fn from(address: &OrchardAddress) -> Self { + *address.inner() + } } /// Serializes an authorized Orchard bundle into the raw fields used by /// state transition constructors. -/// -/// Returns `(actions, flags, value_balance, anchor, proof, binding_signature)`. pub fn serialize_authorized_bundle( bundle: &Bundle, -) -> (Vec, u8, i64, [u8; 32], Vec, [u8; 64]) { +) -> SerializedBundle { let actions: Vec = bundle .actions() .iter() @@ -111,8 +118,15 @@ pub fn serialize_authorized_bundle( let value_balance = *bundle.value_balance(); let anchor = bundle.anchor().to_bytes(); let proof = bundle.authorization().proof().as_ref().to_vec(); - let binding_sig = <[u8; 64]>::from(bundle.authorization().binding_signature()); - (actions, flags, value_balance, anchor, proof, binding_sig) + let binding_signature = <[u8; 64]>::from(bundle.authorization().binding_signature()); + SerializedBundle { + actions, + flags, + value_balance, + anchor, + proof, + binding_signature, + } } // --------------------------------------------------------------------------- @@ -129,7 +143,7 @@ fn build_output_only_bundle( memo: [u8; 36], proving_key: &ProvingKey, ) -> Result, ProtocolError> { - let payment_address = orchard_address_to_payment_address(recipient)?; + let payment_address = PaymentAddress::from(recipient); let anchor = Anchor::empty_tree(); let mut builder = Builder::::new( BundleType::Transactional { @@ -161,7 +175,7 @@ fn build_spend_bundle( proving_key: &ProvingKey, extra_sighash_data: &[u8], ) -> Result, ProtocolError> { - let payment_address = orchard_address_to_payment_address(recipient)?; + let payment_address = PaymentAddress::from(recipient); let mut builder = Builder::::new(BundleType::DEFAULT, anchor); @@ -247,17 +261,16 @@ pub fn build_shield_transition>( } let bundle = build_output_only_bundle(recipient, shield_amount, memo, proving_key)?; - let (actions, flags, value_balance, anchor, proof, binding_sig) = - serialize_authorized_bundle(&bundle); + let sb = serialize_authorized_bundle(&bundle); ShieldTransition::try_from_bundle_with_signer( inputs, - actions, - flags, - value_balance, - anchor, - proof, - binding_sig, + sb.actions, + sb.flags, + sb.value_balance, + sb.anchor, + sb.proof, + sb.binding_signature, fee_strategy, signer, user_fee_increase, @@ -290,18 +303,17 @@ pub fn build_shield_from_asset_lock_transition( platform_version: &PlatformVersion, ) -> Result { let bundle = build_output_only_bundle(recipient, shield_amount, memo, proving_key)?; - let (actions, flags, value_balance, anchor, proof, binding_sig) = - serialize_authorized_bundle(&bundle); + let sb = serialize_authorized_bundle(&bundle); ShieldFromAssetLockTransition::try_from_asset_lock_with_bundle( asset_lock_proof, asset_lock_private_key, - actions, - flags, - value_balance, - anchor, - proof, - binding_sig, + sb.actions, + sb.flags, + sb.value_balance, + sb.anchor, + sb.proof, + sb.binding_signature, user_fee_increase, platform_version, ) @@ -368,7 +380,7 @@ pub fn build_shielded_transfer_transition( let change_amount = total_spent - required; - let recipient_payment = orchard_address_to_payment_address(recipient)?; + let recipient_payment = PaymentAddress::from(recipient); let mut builder = Builder::::new(BundleType::DEFAULT, anchor); @@ -390,7 +402,7 @@ pub fn build_shielded_transfer_transition( // Change output (if any) if change_amount > 0 { - let change_payment = orchard_address_to_payment_address(change_address)?; + let change_payment = PaymentAddress::from(change_address); builder .add_output( None, @@ -403,17 +415,16 @@ pub fn build_shielded_transfer_transition( // ShieldedTransfer has no extra_data in sighash let bundle = prove_and_sign_bundle(builder, proving_key, &[ask.clone()], &[])?; - let (actions, flags, value_balance, anchor_bytes, proof, binding_sig) = - serialize_authorized_bundle(&bundle); + let sb = serialize_authorized_bundle(&bundle); // value_balance = effective_fee (the amount leaving the shielded pool as fee) ShieldedTransferTransition::try_from_bundle( - actions, - flags, - value_balance as u64, - anchor_bytes, - proof, - binding_sig, + sb.actions, + sb.flags, + sb.value_balance as u64, + sb.anchor, + sb.proof, + sb.binding_signature, platform_version, ) } @@ -494,18 +505,17 @@ pub fn build_unshield_transition( &extra_sighash_data, )?; - let (actions, flags, value_balance, anchor_bytes, proof, binding_sig) = - serialize_authorized_bundle(&bundle); + let sb = serialize_authorized_bundle(&bundle); UnshieldTransition::try_from_bundle( output_address, unshield_amount, - actions, - flags, - value_balance, - anchor_bytes, - proof, - binding_sig, + sb.actions, + sb.flags, + sb.value_balance, + sb.anchor, + sb.proof, + sb.binding_signature, platform_version, ) } @@ -592,17 +602,16 @@ pub fn build_shielded_withdrawal_transition( &extra_sighash_data, )?; - let (actions, flags, value_balance, anchor_bytes, proof, binding_sig) = - serialize_authorized_bundle(&bundle); + let sb = serialize_authorized_bundle(&bundle); ShieldedWithdrawalTransition::try_from_bundle( withdrawal_amount, - actions, - flags, - value_balance, - anchor_bytes, - proof, - binding_sig, + sb.actions, + sb.flags, + sb.value_balance, + sb.anchor, + sb.proof, + sb.binding_signature, core_fee_per_byte, pooling, output_script, diff --git a/packages/rs-platform-version/src/version/drive_versions/v7.rs b/packages/rs-platform-version/src/version/drive_versions/v7.rs index f0af92ec592..f6a0abbc499 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v7.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v7.rs @@ -27,7 +27,7 @@ pub const DRIVE_VERSION_V7: DriveVersion = DriveVersion { structure: DRIVE_STRUCTURE_V1, methods: DriveMethodVersions { initialization: DriveInitializationMethodVersions { - create_initial_state_structure: 3, + create_initial_state_structure: 3, // changed: adds shielded pool trees (commitment tree, nullifiers, anchors) }, credit_pools: CREDIT_POOL_METHOD_VERSIONS_V1, protocol_upgrade: DriveProtocolUpgradeVersions { @@ -49,9 +49,9 @@ pub const DRIVE_VERSION_V7: DriveVersion = DriveVersion { add_to_system_credits_operations: 0, remove_from_system_credits: 0, remove_from_system_credits_operations: 0, - calculate_total_credits_balance: 1, // Changed because we now add the address trees + calculate_total_credits_balance: 1, }, - document: DRIVE_DOCUMENT_METHOD_VERSIONS_V2, // Changed + document: DRIVE_DOCUMENT_METHOD_VERSIONS_V2, vote: DRIVE_VOTE_METHOD_VERSIONS_V2, contract: DRIVE_CONTRACT_METHOD_VERSIONS_V2, fees: DriveFeesMethodVersions { calculate_fee: 0 }, @@ -83,7 +83,7 @@ pub const DRIVE_VERSION_V7: DriveVersion = DriveVersion { apply_batch_low_level_drive_operations: 0, apply_batch_grovedb_operations: 0, }, - state_transitions: DRIVE_STATE_TRANSITION_METHOD_VERSIONS_V2, //changed + state_transitions: DRIVE_STATE_TRANSITION_METHOD_VERSIONS_V2, batch_operations: DriveBatchOperationsMethodVersion { convert_drive_operations_to_grove_operations: 0, apply_drive_operations: 0, diff --git a/packages/rs-platform-version/src/version/v12.rs b/packages/rs-platform-version/src/version/v12.rs index b7dd3bd9986..c14b80facb6 100644 --- a/packages/rs-platform-version/src/version/v12.rs +++ b/packages/rs-platform-version/src/version/v12.rs @@ -33,7 +33,7 @@ pub const PROTOCOL_VERSION_12: ProtocolVersion = 12; /// This version is for Platform release 3.1.0 pub const PLATFORM_V12: PlatformVersion = PlatformVersion { protocol_version: PROTOCOL_VERSION_12, - drive: DRIVE_VERSION_V7, + drive: DRIVE_VERSION_V7, // changed: shielded pool (commitment tree, nullifiers, anchors, address funds, sinsemilla hashing) drive_abci: DriveAbciVersion { structs: DRIVE_ABCI_STRUCTURE_VERSIONS_V1, methods: DRIVE_ABCI_METHOD_VERSIONS_V7, diff --git a/packages/rs-sdk-ffi/src/system/queries/path_elements.rs b/packages/rs-sdk-ffi/src/system/queries/path_elements.rs index a8c6d03d801..44c588aeb76 100644 --- a/packages/rs-sdk-ffi/src/system/queries/path_elements.rs +++ b/packages/rs-sdk-ffi/src/system/queries/path_elements.rs @@ -156,7 +156,10 @@ fn get_path_elements( Element::ProvableCountSumTree(_, count, sum, _) => { format!("provable_count_sum_tree:{}:{}", count, sum) } - _ => format!("{:?}", element), + Element::CommitmentTree(_, _, _) => todo!("CommitmentTree display"), + Element::MmrTree(_, _) => todo!("MmrTree display"), + Element::BulkAppendTree(_, _, _) => todo!("BulkAppendTree display"), + Element::DenseAppendOnlyFixedSizeTree(_, _, _) => todo!("DenseAppendOnlyFixedSizeTree display"), }; format!( @@ -177,7 +180,10 @@ fn get_path_elements( Element::ProvableCountSumTree(_, _, _, _) => { "provable_count_sum_tree" } - _ => "unknown", + Element::CommitmentTree(_, _, _) => "commitment_tree", + Element::MmrTree(_, _) => "mmr_tree", + Element::BulkAppendTree(_, _, _) => "bulk_append_tree", + Element::DenseAppendOnlyFixedSizeTree(_, _, _) => "dense_append_only_fixed_size_tree", } ) }) diff --git a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs index bf7c033473b..9de22328d0b 100644 --- a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs +++ b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs @@ -88,7 +88,12 @@ use dpp::consensus::state::prefunded_specialized_balances::prefunded_specialized use dpp::consensus::state::prefunded_specialized_balances::prefunded_specialized_balance_not_found_error::PrefundedSpecializedBalanceNotFoundError; use dpp::consensus::state::token::{IdentityDoesNotHaveEnoughTokenBalanceError, IdentityTokenAccountNotFrozenError, IdentityTokenAccountFrozenError, TokenIsPausedError, IdentityTokenAccountAlreadyFrozenError, UnauthorizedTokenActionError, TokenSettingMaxSupplyToLessThanCurrentSupplyError, TokenMintPastMaxSupplyError, NewTokensDestinationIdentityDoesNotExistError, NewAuthorizedActionTakerIdentityDoesNotExistError, NewAuthorizedActionTakerGroupDoesNotExistError, NewAuthorizedActionTakerMainGroupNotSetError, InvalidGroupPositionError, TokenAlreadyPausedError, TokenNotPausedError, InvalidTokenClaimPropertyMismatch, InvalidTokenClaimNoCurrentRewards, InvalidTokenClaimWrongClaimant, TokenTransferRecipientIdentityNotExistError, PreProgrammedDistributionTimestampInPastError, IdentityHasNotAgreedToPayRequiredTokenAmountError, RequiredTokenPaymentInfoNotSetError, IdentityTryingToPayWithWrongTokenError, TokenDirectPurchaseUserPriceTooLow, TokenAmountUnderMinimumSaleAmount, TokenNotForDirectSale, InvalidTokenPositionStateError}; use dpp::consensus::state::address_funds::{AddressDoesNotExistError, AddressInvalidNonceError, AddressNotEnoughFundsError, AddressesNotEnoughFundsError}; -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}; +use dpp::consensus::state::shielded::insufficient_pool_notes_error::InsufficientPoolNotesError; +use dpp::consensus::state::shielded::insufficient_shielded_fee_error::InsufficientShieldedFeeError; +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, UnshieldAmountZeroError, UnshieldValueBalanceBelowAmountError}; 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; @@ -443,6 +448,21 @@ pub fn from_state_error(state_error: &StateError) -> JsValue { StateError::AddressInvalidNonceError(e) => { generic_consensus_error!(AddressInvalidNonceError, e).into() } + StateError::InvalidAnchorError(e) => { + generic_consensus_error!(InvalidAnchorError, e).into() + } + StateError::NullifierAlreadySpentError(e) => { + generic_consensus_error!(NullifierAlreadySpentError, e).into() + } + StateError::InvalidShieldedProofError(e) => { + generic_consensus_error!(InvalidShieldedProofError, e).into() + } + StateError::InsufficientPoolNotesError(e) => { + generic_consensus_error!(InsufficientPoolNotesError, e).into() + } + StateError::InsufficientShieldedFeeError(e) => { + generic_consensus_error!(InsufficientShieldedFeeError, e).into() + } _ => JsError::new(&format!("unsupported state error: {:?}", state_error)).into(), } } @@ -924,6 +944,27 @@ fn from_basic_error(basic_error: &BasicError) -> JsValue { BasicError::WithdrawalBelowMinAmountError(e) => { generic_consensus_error!(WithdrawalBelowMinAmountError, e).into() } + BasicError::ShieldedNoActionsError(e) => { + generic_consensus_error!(ShieldedNoActionsError, e).into() + } + BasicError::ShieldedTooManyActionsError(e) => { + generic_consensus_error!(ShieldedTooManyActionsError, e).into() + } + BasicError::ShieldedEmptyProofError(e) => { + generic_consensus_error!(ShieldedEmptyProofError, e).into() + } + BasicError::ShieldedZeroAnchorError(e) => { + generic_consensus_error!(ShieldedZeroAnchorError, e).into() + } + BasicError::ShieldedInvalidValueBalanceError(e) => { + generic_consensus_error!(ShieldedInvalidValueBalanceError, e).into() + } + BasicError::UnshieldAmountZeroError(e) => { + generic_consensus_error!(UnshieldAmountZeroError, e).into() + } + BasicError::UnshieldValueBalanceBelowAmountError(e) => { + generic_consensus_error!(UnshieldValueBalanceBelowAmountError, e).into() + } _ => JsError::new(&format!("unsupported basic error: {:?}", basic_error)).into(), } } From e63453dd8add38fc19caa6128dd4e81d8b11aa04 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 4 Mar 2026 21:37:50 +0700 Subject: [PATCH 04/30] docs: correct anchor comments to describe Sinsemilla root, not generic Merkle root The anchor in shielded state transitions is specifically the Sinsemilla root of the depth-32 Orchard note commitment tree (over cmx values), not the GroveDB commitment tree state root which also includes the BulkAppendTree. Co-Authored-By: Claude Opus 4.6 --- packages/rs-dpp/src/shielded/builder.rs | 10 ++++++---- packages/rs-dpp/src/shielded/mod.rs | 7 +++++-- .../shield_from_asset_lock_transition/v0/mod.rs | 2 +- .../shielded/shield_transition/v0/mod.rs | 2 +- .../shielded/shielded_transfer_transition/v0/mod.rs | 2 +- .../shielded/shielded_withdrawal_transition/v0/mod.rs | 2 +- .../shielded/unshield_transition/v0/mod.rs | 2 +- 7 files changed, 16 insertions(+), 11 deletions(-) diff --git a/packages/rs-dpp/src/shielded/builder.rs b/packages/rs-dpp/src/shielded/builder.rs index da4c43c5db0..dd103f5e7f5 100644 --- a/packages/rs-dpp/src/shielded/builder.rs +++ b/packages/rs-dpp/src/shielded/builder.rs @@ -76,7 +76,9 @@ pub struct SerializedBundle { pub flags: u8, /// Net value balance (positive = value leaving the shielded pool). pub value_balance: i64, - /// Merkle tree anchor (32 bytes). + /// Sinsemilla root of the Orchard note commitment tree (32 bytes). + /// This is the Orchard `Anchor` — the root hash of the depth-32 Sinsemilla + /// Merkle tree over extracted note commitments (cmx values). pub anchor: [u8; 32], /// Halo 2 proof bytes. pub proof: Vec, @@ -332,7 +334,7 @@ pub fn build_shield_from_asset_lock_transition( /// - `change_address` - Orchard address for change output (if any) /// - `fvk` - Full viewing key for spend authorization /// - `ask` - Spend authorizing key for RedPallas signatures -/// - `anchor` - Merkle root of the commitment tree +/// - `anchor` - Sinsemilla root of the note commitment tree (Orchard Anchor) /// - `proving_key` - Halo 2 proving key /// - `memo` - 36-byte structured memo for the recipient (4-byte type tag + 32-byte payload) /// - `fee` - Optional fee override; if `None`, the minimum fee is computed automatically. @@ -442,7 +444,7 @@ pub fn build_shielded_transfer_transition( /// - `change_address` - Orchard address for change output /// - `fvk` - Full viewing key for spend authorization /// - `ask` - Spend authorizing key for RedPallas signatures -/// - `anchor` - Merkle root of the commitment tree +/// - `anchor` - Sinsemilla root of the note commitment tree (Orchard Anchor) /// - `proving_key` - Halo 2 proving key /// - `memo` - 36-byte structured memo for the change output (4-byte type tag + 32-byte payload) /// - `fee` - Optional fee override; if `None`, the minimum fee is computed automatically. @@ -535,7 +537,7 @@ pub fn build_unshield_transition( /// - `change_address` - Orchard address for change output /// - `fvk` - Full viewing key for spend authorization /// - `ask` - Spend authorizing key for RedPallas signatures -/// - `anchor` - Merkle root of the commitment tree +/// - `anchor` - Sinsemilla root of the note commitment tree (Orchard Anchor) /// - `proving_key` - Halo 2 proving key /// - `memo` - 36-byte structured memo for the change output (4-byte type tag + 32-byte payload) /// - `fee` - Optional fee override; if `None`, the minimum fee is computed automatically. diff --git a/packages/rs-dpp/src/shielded/mod.rs b/packages/rs-dpp/src/shielded/mod.rs index 867e7da0d44..92d5f914cf4 100644 --- a/packages/rs-dpp/src/shielded/mod.rs +++ b/packages/rs-dpp/src/shielded/mod.rs @@ -83,7 +83,7 @@ pub(crate) mod serde_bytes_64 { /// Common Orchard bundle parameters shared across all shielded transition types. /// /// Groups the fields that every shielded transition carries identically: -/// the serialized actions, bundle flags, commitment tree anchor, Halo 2 proof, +/// the serialized actions, bundle flags, Sinsemilla anchor, Halo 2 proof, /// and RedPallas binding signature. Using this struct reduces parameter counts /// in SDK helper functions from 10-12 down to 5-8. pub struct OrchardBundleParams { @@ -91,7 +91,10 @@ pub struct OrchardBundleParams { pub actions: Vec, /// Bundle flags byte. pub flags: u8, - /// Merkle root of the commitment tree at bundle creation time (32 bytes). + /// Sinsemilla root of the note commitment tree at bundle creation time (32 bytes). + /// This is the Orchard Anchor — the root of the depth-32 Sinsemilla Merkle + /// tree over extracted note commitments (cmx values), NOT the GroveDB + /// commitment tree state root. pub anchor: [u8; 32], /// Halo 2 zero-knowledge proof bytes. pub proof: Vec, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs index 716468a9bf0..aaa56f98142 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs @@ -40,7 +40,7 @@ pub struct ShieldFromAssetLockTransitionV0 { pub flags: u8, /// Net value flowing into the shielded pool (must be negative for shielding) pub value_balance: i64, - /// Merkle root of the commitment tree at time of bundle creation + /// Sinsemilla root of the note commitment tree (Orchard Anchor) pub anchor: [u8; 32], /// Halo2 proof bytes pub proof: Vec, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/mod.rs index 1a62c9b5595..cbc7d02a0ac 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/mod.rs @@ -44,7 +44,7 @@ pub struct ShieldTransitionV0 { pub flags: u8, /// Net value flowing into/out of the shielded pool pub value_balance: i64, - /// Merkle root of the commitment tree at time of bundle creation + /// Sinsemilla root of the note commitment tree (Orchard Anchor) pub anchor: [u8; 32], /// Halo2 proof bytes pub proof: Vec, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/mod.rs index 4d1a846b89c..a21e59bcb25 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/mod.rs @@ -34,7 +34,7 @@ pub struct ShieldedTransferTransitionV0 { pub flags: u8, /// Net value balance (fee amount extracted from shielded pool) pub value_balance: u64, - /// Merkle root of the commitment tree used for spends + /// Sinsemilla root of the note commitment tree (Orchard Anchor) pub anchor: [u8; 32], /// Halo2 proof bytes pub proof: Vec, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs index 086cad5f132..0dc7916182a 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs @@ -38,7 +38,7 @@ pub struct ShieldedWithdrawalTransitionV0 { pub flags: u8, /// Net value balance (amount + fee flowing out of shielded pool) pub value_balance: i64, - /// Merkle root of the commitment tree used for spends + /// Sinsemilla root of the note commitment tree (Orchard Anchor) pub anchor: [u8; 32], /// Halo2 proof bytes pub proof: Vec, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs index 989643bf2f6..fd5a2e906df 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs @@ -39,7 +39,7 @@ pub struct UnshieldTransitionV0 { pub flags: u8, /// Net value balance (amount + fee flowing out of shielded pool) pub value_balance: i64, - /// Merkle root of the commitment tree used for spends + /// Sinsemilla root of the note commitment tree (Orchard Anchor) pub anchor: [u8; 32], /// Halo2 proof bytes pub proof: Vec, From 88613b724bd9f5f7f06d85c4d235b3680e556de6 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 4 Mar 2026 21:40:00 +0700 Subject: [PATCH 05/30] style: cargo fmt + remove unreachable catch-all patterns in wasm-dpp Co-Authored-By: Claude Opus 4.6 --- .../src/address_funds/platform_address.rs | 19 ++++++++----------- packages/rs-dpp/src/shielded/builder.rs | 4 +--- .../v0/mod.rs | 2 +- .../src/system/queries/path_elements.rs | 7 +++++-- .../src/errors/consensus/consensus_error.rs | 6 +----- 5 files changed, 16 insertions(+), 22 deletions(-) diff --git a/packages/rs-dpp/src/address_funds/platform_address.rs b/packages/rs-dpp/src/address_funds/platform_address.rs index 5e4987c3be7..5fdcff12711 100644 --- a/packages/rs-dpp/src/address_funds/platform_address.rs +++ b/packages/rs-dpp/src/address_funds/platform_address.rs @@ -612,17 +612,14 @@ impl OrchardAddress { /// /// The first 11 bytes are the diversifier, the next 32 are pk_d. /// Returns an error if `pk_d` is not a valid Pallas curve point. - pub fn from_raw_bytes( - bytes: &[u8; ORCHARD_ADDRESS_SIZE], - ) -> Result { - let addr = Option::from( - grovedb_commitment_tree::PaymentAddress::from_raw_address_bytes(bytes), - ) - .ok_or_else(|| { - ProtocolError::DecodingError( - "OrchardAddress pk_d is not a valid Pallas curve point".to_string(), - ) - })?; + pub fn from_raw_bytes(bytes: &[u8; ORCHARD_ADDRESS_SIZE]) -> Result { + let addr = + Option::from(grovedb_commitment_tree::PaymentAddress::from_raw_address_bytes(bytes)) + .ok_or_else(|| { + ProtocolError::DecodingError( + "OrchardAddress pk_d is not a valid Pallas curve point".to_string(), + ) + })?; Ok(Self(addr)) } diff --git a/packages/rs-dpp/src/shielded/builder.rs b/packages/rs-dpp/src/shielded/builder.rs index dd103f5e7f5..0e01ad3818b 100644 --- a/packages/rs-dpp/src/shielded/builder.rs +++ b/packages/rs-dpp/src/shielded/builder.rs @@ -94,9 +94,7 @@ impl From<&OrchardAddress> for PaymentAddress { /// Serializes an authorized Orchard bundle into the raw fields used by /// state transition constructors. -pub fn serialize_authorized_bundle( - bundle: &Bundle, -) -> SerializedBundle { +pub fn serialize_authorized_bundle(bundle: &Bundle) -> SerializedBundle { let actions: Vec = bundle .actions() .iter() diff --git a/packages/rs-drive/src/util/grove_operations/batch_insert_empty_tree_if_not_exists/v0/mod.rs b/packages/rs-drive/src/util/grove_operations/batch_insert_empty_tree_if_not_exists/v0/mod.rs index f982014d9cd..aa84e0e2646 100644 --- a/packages/rs-drive/src/util/grove_operations/batch_insert_empty_tree_if_not_exists/v0/mod.rs +++ b/packages/rs-drive/src/util/grove_operations/batch_insert_empty_tree_if_not_exists/v0/mod.rs @@ -10,8 +10,8 @@ use crate::util::object_size_info::PathKeyInfo::{ }; use crate::util::storage_flags::StorageFlags; use dpp::version::drive_versions::DriveVersion; -use grovedb::batch::GroveOp; use grovedb::batch::key_info::KeyInfo; +use grovedb::batch::GroveOp; use grovedb::{TransactionArg, TreeType}; impl Drive { diff --git a/packages/rs-sdk-ffi/src/system/queries/path_elements.rs b/packages/rs-sdk-ffi/src/system/queries/path_elements.rs index 44c588aeb76..df94197cf43 100644 --- a/packages/rs-sdk-ffi/src/system/queries/path_elements.rs +++ b/packages/rs-sdk-ffi/src/system/queries/path_elements.rs @@ -159,7 +159,9 @@ fn get_path_elements( Element::CommitmentTree(_, _, _) => todo!("CommitmentTree display"), Element::MmrTree(_, _) => todo!("MmrTree display"), Element::BulkAppendTree(_, _, _) => todo!("BulkAppendTree display"), - Element::DenseAppendOnlyFixedSizeTree(_, _, _) => todo!("DenseAppendOnlyFixedSizeTree display"), + Element::DenseAppendOnlyFixedSizeTree(_, _, _) => { + todo!("DenseAppendOnlyFixedSizeTree display") + } }; format!( @@ -183,7 +185,8 @@ fn get_path_elements( Element::CommitmentTree(_, _, _) => "commitment_tree", Element::MmrTree(_, _) => "mmr_tree", Element::BulkAppendTree(_, _, _) => "bulk_append_tree", - Element::DenseAppendOnlyFixedSizeTree(_, _, _) => "dense_append_only_fixed_size_tree", + Element::DenseAppendOnlyFixedSizeTree(_, _, _) => + "dense_append_only_fixed_size_tree", } ) }) diff --git a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs index 9de22328d0b..f88f9766b50 100644 --- a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs +++ b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs @@ -448,9 +448,7 @@ pub fn from_state_error(state_error: &StateError) -> JsValue { StateError::AddressInvalidNonceError(e) => { generic_consensus_error!(AddressInvalidNonceError, e).into() } - StateError::InvalidAnchorError(e) => { - generic_consensus_error!(InvalidAnchorError, e).into() - } + StateError::InvalidAnchorError(e) => generic_consensus_error!(InvalidAnchorError, e).into(), StateError::NullifierAlreadySpentError(e) => { generic_consensus_error!(NullifierAlreadySpentError, e).into() } @@ -463,7 +461,6 @@ pub fn from_state_error(state_error: &StateError) -> JsValue { StateError::InsufficientShieldedFeeError(e) => { generic_consensus_error!(InsufficientShieldedFeeError, e).into() } - _ => JsError::new(&format!("unsupported state error: {:?}", state_error)).into(), } } @@ -965,7 +962,6 @@ fn from_basic_error(basic_error: &BasicError) -> JsValue { BasicError::UnshieldValueBalanceBelowAmountError(e) => { generic_consensus_error!(UnshieldValueBalanceBelowAmountError, e).into() } - _ => JsError::new(&format!("unsupported basic error: {:?}", basic_error)).into(), } } From 9c5e3b50e2d2bd9982fe2cd0b8baad392f304f89 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 4 Mar 2026 23:36:13 +0700 Subject: [PATCH 06/30] =?UTF-8?q?refactor(dpp):=20address=20PR=20review=20?= =?UTF-8?q?=E2=80=94=20flags=20docs,=20value=5Fbalance=20u64,=20signature?= =?UTF-8?q?=20property=20paths?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Improve flags field documentation on all 5 shielded transitions to explain the Orchard bundle bit layout (bit 0 = spends_enabled, bit 1 = outputs_enabled) with per-transition usage context. - Change ShieldFromAssetLockTransitionV0.value_balance from i64 to u64 since value can only flow one direction (into the pool). Adds validation that value_balance > 0 and <= i64::MAX. Updates builder to negate the Orchard bundle's negative i64 value_balance. - Add signature_property_paths and binary_property_paths returning SIGNATURE for ShieldFromAssetLockTransition (both v0 and enum wrapper) with a new fields.rs module. This transition is the only shielded transition with an ECDSA signature field. Co-Authored-By: Claude Opus 4.6 --- packages/rs-dpp/src/shielded/builder.rs | 14 +++++++++++++- .../fields.rs | 1 + .../methods/mod.rs | 2 +- .../methods/v0/mod.rs | 2 +- .../shield_from_asset_lock_transition/mod.rs | 6 ++++-- .../v0/mod.rs | 12 ++++++++---- .../v0/state_transition_validation.rs | 18 +++++++++++++++--- .../v0/types.rs | 5 +++-- .../v0/v0_methods.rs | 2 +- .../shielded/shield_transition/v0/mod.rs | 5 ++++- .../shielded_transfer_transition/v0/mod.rs | 5 ++++- .../shielded_withdrawal_transition/v0/mod.rs | 5 ++++- .../shielded/unshield_transition/v0/mod.rs | 5 ++++- 13 files changed, 63 insertions(+), 19 deletions(-) create mode 100644 packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/fields.rs diff --git a/packages/rs-dpp/src/shielded/builder.rs b/packages/rs-dpp/src/shielded/builder.rs index 0e01ad3818b..98d67361a4c 100644 --- a/packages/rs-dpp/src/shielded/builder.rs +++ b/packages/rs-dpp/src/shielded/builder.rs @@ -305,12 +305,24 @@ pub fn build_shield_from_asset_lock_transition( let bundle = build_output_only_bundle(recipient, shield_amount, memo, proving_key)?; let sb = serialize_authorized_bundle(&bundle); + // For output-only bundles, Orchard value_balance is negative (value flowing in). + // Convert to u64 (absolute amount entering the pool). + let value_balance = sb + .value_balance + .checked_neg() + .and_then(|v| u64::try_from(v).ok()) + .ok_or_else(|| { + ProtocolError::Generic( + "shield_from_asset_lock: bundle value_balance is not negative".to_string(), + ) + })?; + ShieldFromAssetLockTransition::try_from_asset_lock_with_bundle( asset_lock_proof, asset_lock_private_key, sb.actions, sb.flags, - sb.value_balance, + value_balance, sb.anchor, sb.proof, sb.binding_signature, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/fields.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/fields.rs new file mode 100644 index 00000000000..ef367006ce7 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/fields.rs @@ -0,0 +1 @@ +pub use crate::state_transition::state_transitions::common_fields::property_names::SIGNATURE; diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/mod.rs index 5b5288351aa..d70a444f2a9 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/mod.rs @@ -25,7 +25,7 @@ impl ShieldFromAssetLockTransitionMethodsV0 for ShieldFromAssetLockTransition { asset_lock_proof_private_key: &[u8], actions: Vec, flags: u8, - value_balance: i64, + value_balance: u64, anchor: [u8; 32], proof: Vec, binding_signature: [u8; 64], diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/v0/mod.rs index 49fbb3f59d4..2b33fb74037 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/v0/mod.rs @@ -16,7 +16,7 @@ pub trait ShieldFromAssetLockTransitionMethodsV0 { asset_lock_proof_private_key: &[u8], actions: Vec, flags: u8, - value_balance: i64, + value_balance: u64, anchor: [u8; 32], proof: Vec, binding_signature: [u8; 64], diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/mod.rs index 4d496b73de7..776eafeabfe 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/mod.rs @@ -1,3 +1,4 @@ +pub mod fields; pub mod methods; mod proved; mod state_transition_estimated_fee_validation; @@ -6,6 +7,7 @@ mod state_transition_validation; pub mod v0; mod version; +use crate::state_transition::shield_from_asset_lock_transition::fields::SIGNATURE; use crate::state_transition::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0; use crate::state_transition::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0Signable; use crate::state_transition::StateTransitionFieldTypes; @@ -48,7 +50,7 @@ pub enum ShieldFromAssetLockTransition { impl StateTransitionFieldTypes for ShieldFromAssetLockTransition { fn signature_property_paths() -> Vec<&'static str> { - vec![] + vec![SIGNATURE] } fn identifiers_property_paths() -> Vec<&'static str> { @@ -56,6 +58,6 @@ impl StateTransitionFieldTypes for ShieldFromAssetLockTransition { } fn binary_property_paths() -> Vec<&'static str> { - vec![] + vec![SIGNATURE] } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs index aaa56f98142..ea8b6c8e46b 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs @@ -36,10 +36,14 @@ pub struct ShieldFromAssetLockTransitionV0 { pub asset_lock_proof: AssetLockProof, /// Orchard actions (spend-output pairs) pub actions: Vec, - /// Bundle flags (spends_enabled | outputs_enabled) + /// Orchard bundle flags controlling which half of actions are enabled. + /// Bit 0 (0x01): spends_enabled — set if actions may spend existing notes. + /// Bit 1 (0x02): outputs_enabled — set if actions may create new notes. + /// For a shield (output-only) bundle, only outputs_enabled (0x02) is set. pub flags: u8, - /// Net value flowing into the shielded pool (must be negative for shielding) - pub value_balance: i64, + /// Amount of credits flowing into the shielded pool from the asset lock. + /// Must be > 0 and <= i64::MAX. + pub value_balance: u64, /// Sinsemilla root of the note commitment tree (Orchard Anchor) pub anchor: [u8; 32], /// Halo2 proof bytes @@ -94,7 +98,7 @@ mod tests { spend_auth_sig: [6u8; 64], }], flags: 0u8, - value_balance: -1000i64, + value_balance: 1000u64, anchor: [7u8; 32], proof: vec![8u8; 100], binding_signature: [9u8; 64], 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 cd9c691f79b..84ca071d606 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 @@ -23,12 +23,24 @@ impl StateTransitionStructureValidation for ShieldFromAssetLockTransitionV0 { return err; } - // value_balance must be negative (credits flowing into pool) - if self.value_balance >= 0 { + // value_balance must be > 0 (credits flowing into pool) + if self.value_balance == 0 { return SimpleConsensusValidationResult::new_with_error( BasicError::ShieldedInvalidValueBalanceError( ShieldedInvalidValueBalanceError::new( - "shield_from_asset_lock value_balance must be negative".to_string(), + "shield_from_asset_lock value_balance must be greater than 0".to_string(), + ), + ) + .into(), + ); + } + + // value_balance must fit in i64 (Orchard protocol uses i64 internally) + if self.value_balance > i64::MAX as u64 { + return SimpleConsensusValidationResult::new_with_error( + BasicError::ShieldedInvalidValueBalanceError( + ShieldedInvalidValueBalanceError::new( + "shield_from_asset_lock value_balance exceeds i64::MAX".to_string(), ), ) .into(), diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/types.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/types.rs index 09acbb4fe11..37cccd61813 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/types.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/types.rs @@ -1,9 +1,10 @@ +use crate::state_transition::shield_from_asset_lock_transition::fields::SIGNATURE; use crate::state_transition::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0; use crate::state_transition::StateTransitionFieldTypes; impl StateTransitionFieldTypes for ShieldFromAssetLockTransitionV0 { fn signature_property_paths() -> Vec<&'static str> { - vec![] + vec![SIGNATURE] } fn identifiers_property_paths() -> Vec<&'static str> { @@ -11,6 +12,6 @@ impl StateTransitionFieldTypes for ShieldFromAssetLockTransitionV0 { } fn binary_property_paths() -> Vec<&'static str> { - vec![] + vec![SIGNATURE] } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/v0_methods.rs index baafa61a098..63d26a6e783 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/v0_methods.rs @@ -20,7 +20,7 @@ impl ShieldFromAssetLockTransitionMethodsV0 for ShieldFromAssetLockTransitionV0 asset_lock_proof_private_key: &[u8], actions: Vec, flags: u8, - value_balance: i64, + value_balance: u64, anchor: [u8; 32], proof: Vec, binding_signature: [u8; 64], diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/mod.rs index cbc7d02a0ac..5b82e91ac3e 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/mod.rs @@ -40,7 +40,10 @@ pub struct ShieldTransitionV0 { pub inputs: BTreeMap, /// Orchard actions (spend-output pairs) pub actions: Vec, - /// Bundle flags (spends_enabled | outputs_enabled) + /// Orchard bundle flags controlling which half of actions are enabled. + /// Bit 0 (0x01): spends_enabled — set if actions may spend existing notes. + /// Bit 1 (0x02): outputs_enabled — set if actions may create new notes. + /// For a shield (output-only) bundle, only outputs_enabled (0x02) is set. pub flags: u8, /// Net value flowing into/out of the shielded pool pub value_balance: i64, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/mod.rs index a21e59bcb25..36cb22dfe55 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/mod.rs @@ -30,7 +30,10 @@ use serde::{Deserialize, Serialize}; pub struct ShieldedTransferTransitionV0 { /// Orchard actions (spend-output pairs) pub actions: Vec, - /// Bundle flags (spends_enabled | outputs_enabled) + /// Orchard bundle flags controlling which half of actions are enabled. + /// Bit 0 (0x01): spends_enabled — set if actions may spend existing notes. + /// Bit 1 (0x02): outputs_enabled — set if actions may create new notes. + /// For a transfer both bits are set (0x03). pub flags: u8, /// Net value balance (fee amount extracted from shielded pool) pub value_balance: u64, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs index 0dc7916182a..5ae528bf53c 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs @@ -34,7 +34,10 @@ pub struct ShieldedWithdrawalTransitionV0 { pub amount: u64, /// Orchard actions (spends + change outputs) pub actions: Vec, - /// Bundle flags (spends_enabled | outputs_enabled) + /// Orchard bundle flags controlling which half of actions are enabled. + /// Bit 0 (0x01): spends_enabled — set if actions may spend existing notes. + /// Bit 1 (0x02): outputs_enabled — set if actions may create new notes. + /// For a withdrawal (spend + change) both bits are set (0x03). pub flags: u8, /// Net value balance (amount + fee flowing out of shielded pool) pub value_balance: i64, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs index fd5a2e906df..df7356c8c9a 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs @@ -35,7 +35,10 @@ pub struct UnshieldTransitionV0 { pub amount: u64, /// Orchard actions (spend-output pairs) pub actions: Vec, - /// Bundle flags (spends_enabled | outputs_enabled) + /// Orchard bundle flags controlling which half of actions are enabled. + /// Bit 0 (0x01): spends_enabled — set if actions may spend existing notes. + /// Bit 1 (0x02): outputs_enabled — set if actions may create new notes. + /// For an unshield (spend + change) both bits are set (0x03). pub flags: u8, /// Net value balance (amount + fee flowing out of shielded pool) pub value_balance: i64, From bab09369955acf3d43912cf2ea67bc219799037f Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 4 Mar 2026 23:47:17 +0700 Subject: [PATCH 07/30] refactor(dpp): remove flags field from all shielded transition structs The Orchard bundle flags are deterministic per transition type: - Shield/ShieldFromAssetLock: always 0x02 (outputs_enabled only) - Transfer/Unshield/Withdrawal: always 0x03 (both enabled) No need to store or pass them through. The flags are still extracted from the bundle in SerializedBundle for internal use but are no longer part of the on-chain state transition representation. Co-Authored-By: Claude Opus 4.6 --- packages/rs-dpp/src/shielded/builder.rs | 5 ----- .../shield_from_asset_lock_transition/methods/mod.rs | 2 -- .../shield_from_asset_lock_transition/methods/v0/mod.rs | 1 - .../shielded/shield_from_asset_lock_transition/v0/mod.rs | 6 ------ .../shield_from_asset_lock_transition/v0/v0_methods.rs | 2 -- .../shielded/shield_transition/methods/mod.rs | 2 -- .../shielded/shield_transition/methods/v0/mod.rs | 1 - .../state_transitions/shielded/shield_transition/v0/mod.rs | 6 ------ .../shielded/shield_transition/v0/v0_methods.rs | 2 -- .../shielded/shielded_transfer_transition/methods/mod.rs | 2 -- .../shielded/shielded_transfer_transition/methods/v0/mod.rs | 1 - .../shielded/shielded_transfer_transition/v0/mod.rs | 6 ------ .../shielded/shielded_transfer_transition/v0/v0_methods.rs | 2 -- .../shielded/shielded_withdrawal_transition/methods/mod.rs | 2 -- .../shielded_withdrawal_transition/methods/v0/mod.rs | 1 - .../shielded/shielded_withdrawal_transition/v0/mod.rs | 6 ------ .../shielded_withdrawal_transition/v0/v0_methods.rs | 2 -- .../shielded/unshield_transition/methods/mod.rs | 2 -- .../shielded/unshield_transition/methods/v0/mod.rs | 1 - .../shielded/unshield_transition/v0/mod.rs | 6 ------ .../shielded/unshield_transition/v0/v0_methods.rs | 2 -- 21 files changed, 60 deletions(-) diff --git a/packages/rs-dpp/src/shielded/builder.rs b/packages/rs-dpp/src/shielded/builder.rs index 98d67361a4c..6ef350474fb 100644 --- a/packages/rs-dpp/src/shielded/builder.rs +++ b/packages/rs-dpp/src/shielded/builder.rs @@ -266,7 +266,6 @@ pub fn build_shield_transition>( ShieldTransition::try_from_bundle_with_signer( inputs, sb.actions, - sb.flags, sb.value_balance, sb.anchor, sb.proof, @@ -321,7 +320,6 @@ pub fn build_shield_from_asset_lock_transition( asset_lock_proof, asset_lock_private_key, sb.actions, - sb.flags, value_balance, sb.anchor, sb.proof, @@ -432,7 +430,6 @@ pub fn build_shielded_transfer_transition( // value_balance = effective_fee (the amount leaving the shielded pool as fee) ShieldedTransferTransition::try_from_bundle( sb.actions, - sb.flags, sb.value_balance as u64, sb.anchor, sb.proof, @@ -523,7 +520,6 @@ pub fn build_unshield_transition( output_address, unshield_amount, sb.actions, - sb.flags, sb.value_balance, sb.anchor, sb.proof, @@ -619,7 +615,6 @@ pub fn build_shielded_withdrawal_transition( ShieldedWithdrawalTransition::try_from_bundle( withdrawal_amount, sb.actions, - sb.flags, sb.value_balance, sb.anchor, sb.proof, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/mod.rs index d70a444f2a9..4192559df0c 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/mod.rs @@ -24,7 +24,6 @@ impl ShieldFromAssetLockTransitionMethodsV0 for ShieldFromAssetLockTransition { asset_lock_proof: AssetLockProof, asset_lock_proof_private_key: &[u8], actions: Vec, - flags: u8, value_balance: u64, anchor: [u8; 32], proof: Vec, @@ -42,7 +41,6 @@ impl ShieldFromAssetLockTransitionMethodsV0 for ShieldFromAssetLockTransition { asset_lock_proof, asset_lock_proof_private_key, actions, - flags, value_balance, anchor, proof, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/v0/mod.rs index 2b33fb74037..6a91d10cd31 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/v0/mod.rs @@ -15,7 +15,6 @@ pub trait ShieldFromAssetLockTransitionMethodsV0 { asset_lock_proof: AssetLockProof, asset_lock_proof_private_key: &[u8], actions: Vec, - flags: u8, value_balance: u64, anchor: [u8; 32], proof: Vec, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs index ea8b6c8e46b..bbb4787dc62 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs @@ -36,11 +36,6 @@ pub struct ShieldFromAssetLockTransitionV0 { pub asset_lock_proof: AssetLockProof, /// Orchard actions (spend-output pairs) pub actions: Vec, - /// Orchard bundle flags controlling which half of actions are enabled. - /// Bit 0 (0x01): spends_enabled — set if actions may spend existing notes. - /// Bit 1 (0x02): outputs_enabled — set if actions may create new notes. - /// For a shield (output-only) bundle, only outputs_enabled (0x02) is set. - pub flags: u8, /// Amount of credits flowing into the shielded pool from the asset lock. /// Must be > 0 and <= i64::MAX. pub value_balance: u64, @@ -97,7 +92,6 @@ mod tests { cv_net: [5u8; 32], spend_auth_sig: [6u8; 64], }], - flags: 0u8, value_balance: 1000u64, anchor: [7u8; 32], proof: vec![8u8; 100], diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/v0_methods.rs index 63d26a6e783..9df7f00b518 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/v0_methods.rs @@ -19,7 +19,6 @@ impl ShieldFromAssetLockTransitionMethodsV0 for ShieldFromAssetLockTransitionV0 asset_lock_proof: AssetLockProof, asset_lock_proof_private_key: &[u8], actions: Vec, - flags: u8, value_balance: u64, anchor: [u8; 32], proof: Vec, @@ -31,7 +30,6 @@ impl ShieldFromAssetLockTransitionMethodsV0 for ShieldFromAssetLockTransitionV0 let mut transition = ShieldFromAssetLockTransitionV0 { asset_lock_proof, actions, - flags, value_balance, anchor, proof, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/methods/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/methods/mod.rs index fa612ecbeb2..6d11ed71562 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/methods/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/methods/mod.rs @@ -27,7 +27,6 @@ impl ShieldTransitionMethodsV0 for ShieldTransition { fn try_from_bundle_with_signer>( inputs: BTreeMap, actions: Vec, - flags: u8, value_balance: i64, anchor: [u8; 32], proof: Vec, @@ -46,7 +45,6 @@ impl ShieldTransitionMethodsV0 for ShieldTransition { 0 => ShieldTransitionV0::try_from_bundle_with_signer( inputs, actions, - flags, value_balance, anchor, proof, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/methods/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/methods/v0/mod.rs index 49f1d4d29e3..05a88aed176 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/methods/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/methods/v0/mod.rs @@ -25,7 +25,6 @@ pub trait ShieldTransitionMethodsV0 { fn try_from_bundle_with_signer>( inputs: BTreeMap, actions: Vec, - flags: u8, value_balance: i64, anchor: [u8; 32], proof: Vec, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/mod.rs index 5b82e91ac3e..c8d04c2074a 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/mod.rs @@ -40,11 +40,6 @@ pub struct ShieldTransitionV0 { pub inputs: BTreeMap, /// Orchard actions (spend-output pairs) pub actions: Vec, - /// Orchard bundle flags controlling which half of actions are enabled. - /// Bit 0 (0x01): spends_enabled — set if actions may spend existing notes. - /// Bit 1 (0x02): outputs_enabled — set if actions may create new notes. - /// For a shield (output-only) bundle, only outputs_enabled (0x02) is set. - pub flags: u8, /// Net value flowing into/out of the shielded pool pub value_balance: i64, /// Sinsemilla root of the note commitment tree (Orchard Anchor) @@ -101,7 +96,6 @@ mod tests { cv_net: [5u8; 32], spend_auth_sig: [6u8; 64], }], - flags: 0u8, value_balance: -1000i64, anchor: [7u8; 32], proof: vec![8u8; 100], diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/v0_methods.rs index 953d5002a26..e8d41ad4dd1 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/v0_methods.rs @@ -27,7 +27,6 @@ impl ShieldTransitionMethodsV0 for ShieldTransitionV0 { fn try_from_bundle_with_signer>( inputs: BTreeMap, actions: Vec, - flags: u8, value_balance: i64, anchor: [u8; 32], proof: Vec, @@ -41,7 +40,6 @@ impl ShieldTransitionMethodsV0 for ShieldTransitionV0 { let mut shield_transition = ShieldTransitionV0 { inputs: inputs.clone(), actions, - flags, value_balance, anchor, proof, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/methods/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/methods/mod.rs index 9e31072690d..b3190031954 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/methods/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/methods/mod.rs @@ -19,7 +19,6 @@ impl ShieldedTransferTransitionMethodsV0 for ShieldedTransferTransition { #[cfg(feature = "state-transition-signing")] fn try_from_bundle( actions: Vec, - flags: u8, value_balance: u64, anchor: [u8; 32], proof: Vec, @@ -34,7 +33,6 @@ impl ShieldedTransferTransitionMethodsV0 for ShieldedTransferTransition { { 0 => ShieldedTransferTransitionV0::try_from_bundle( actions, - flags, value_balance, anchor, proof, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/methods/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/methods/v0/mod.rs index 25a726a6182..90b8fbeff2a 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/methods/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/methods/v0/mod.rs @@ -10,7 +10,6 @@ pub trait ShieldedTransferTransitionMethodsV0 { #[cfg(feature = "state-transition-signing")] fn try_from_bundle( actions: Vec, - flags: u8, value_balance: u64, anchor: [u8; 32], proof: Vec, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/mod.rs index 36cb22dfe55..d973cb2607a 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/mod.rs @@ -30,11 +30,6 @@ use serde::{Deserialize, Serialize}; pub struct ShieldedTransferTransitionV0 { /// Orchard actions (spend-output pairs) pub actions: Vec, - /// Orchard bundle flags controlling which half of actions are enabled. - /// Bit 0 (0x01): spends_enabled — set if actions may spend existing notes. - /// Bit 1 (0x02): outputs_enabled — set if actions may create new notes. - /// For a transfer both bits are set (0x03). - pub flags: u8, /// Net value balance (fee amount extracted from shielded pool) pub value_balance: u64, /// Sinsemilla root of the note commitment tree (Orchard Anchor) @@ -77,7 +72,6 @@ mod tests { cv_net: [5u8; 32], spend_auth_sig: [6u8; 64], }], - flags: 0u8, value_balance: 0u64, anchor: [7u8; 32], proof: vec![8u8; 100], diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/v0_methods.rs index 225d520c81f..b9971987196 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/v0_methods.rs @@ -11,7 +11,6 @@ impl ShieldedTransferTransitionMethodsV0 for ShieldedTransferTransitionV0 { #[cfg(feature = "state-transition-signing")] fn try_from_bundle( actions: Vec, - flags: u8, value_balance: u64, anchor: [u8; 32], proof: Vec, @@ -20,7 +19,6 @@ impl ShieldedTransferTransitionMethodsV0 for ShieldedTransferTransitionV0 { ) -> Result { let transition = ShieldedTransferTransitionV0 { actions, - flags, value_balance, anchor, proof, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/mod.rs index ba8da275dbf..818fb952389 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/mod.rs @@ -24,7 +24,6 @@ impl ShieldedWithdrawalTransitionMethodsV0 for ShieldedWithdrawalTransition { fn try_from_bundle( amount: u64, actions: Vec, - flags: u8, value_balance: i64, anchor: [u8; 32], proof: Vec, @@ -43,7 +42,6 @@ impl ShieldedWithdrawalTransitionMethodsV0 for ShieldedWithdrawalTransition { 0 => ShieldedWithdrawalTransitionV0::try_from_bundle( amount, actions, - flags, value_balance, anchor, proof, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/v0/mod.rs index 53f33aca6f1..3c2f025cffc 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/v0/mod.rs @@ -16,7 +16,6 @@ pub trait ShieldedWithdrawalTransitionMethodsV0 { fn try_from_bundle( amount: u64, actions: Vec, - flags: u8, value_balance: i64, anchor: [u8; 32], proof: Vec, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs index 5ae528bf53c..9d71f97f0d6 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs @@ -34,11 +34,6 @@ pub struct ShieldedWithdrawalTransitionV0 { pub amount: u64, /// Orchard actions (spends + change outputs) pub actions: Vec, - /// Orchard bundle flags controlling which half of actions are enabled. - /// Bit 0 (0x01): spends_enabled — set if actions may spend existing notes. - /// Bit 1 (0x02): outputs_enabled — set if actions may create new notes. - /// For a withdrawal (spend + change) both bits are set (0x03). - pub flags: u8, /// Net value balance (amount + fee flowing out of shielded pool) pub value_balance: i64, /// Sinsemilla root of the note commitment tree (Orchard Anchor) @@ -88,7 +83,6 @@ mod tests { cv_net: [5u8; 32], spend_auth_sig: [6u8; 64], }], - flags: 0u8, value_balance: 1000i64, anchor: [7u8; 32], proof: vec![8u8; 100], diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/v0_methods.rs index a3767b3bb73..b58e01b3fa6 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/v0_methods.rs @@ -16,7 +16,6 @@ impl ShieldedWithdrawalTransitionMethodsV0 for ShieldedWithdrawalTransitionV0 { fn try_from_bundle( amount: u64, actions: Vec, - flags: u8, value_balance: i64, anchor: [u8; 32], proof: Vec, @@ -29,7 +28,6 @@ impl ShieldedWithdrawalTransitionMethodsV0 for ShieldedWithdrawalTransitionV0 { let transition = ShieldedWithdrawalTransitionV0 { amount, actions, - flags, value_balance, anchor, proof, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/mod.rs index 816dc13c9bd..11c029578d4 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/mod.rs @@ -21,7 +21,6 @@ impl UnshieldTransitionMethodsV0 for UnshieldTransition { output_address: PlatformAddress, amount: u64, actions: Vec, - flags: u8, value_balance: i64, anchor: [u8; 32], proof: Vec, @@ -38,7 +37,6 @@ impl UnshieldTransitionMethodsV0 for UnshieldTransition { output_address, amount, actions, - flags, value_balance, anchor, proof, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/v0/mod.rs index 0842fdd09a4..371e3dc8c1a 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/v0/mod.rs @@ -15,7 +15,6 @@ pub trait UnshieldTransitionMethodsV0 { output_address: PlatformAddress, amount: u64, actions: Vec, - flags: u8, value_balance: i64, anchor: [u8; 32], proof: Vec, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs index df7356c8c9a..8ed98602ea6 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs @@ -35,11 +35,6 @@ pub struct UnshieldTransitionV0 { pub amount: u64, /// Orchard actions (spend-output pairs) pub actions: Vec, - /// Orchard bundle flags controlling which half of actions are enabled. - /// Bit 0 (0x01): spends_enabled — set if actions may spend existing notes. - /// Bit 1 (0x02): outputs_enabled — set if actions may create new notes. - /// For an unshield (spend + change) both bits are set (0x03). - pub flags: u8, /// Net value balance (amount + fee flowing out of shielded pool) pub value_balance: i64, /// Sinsemilla root of the note commitment tree (Orchard Anchor) @@ -87,7 +82,6 @@ mod tests { cv_net: [5u8; 32], spend_auth_sig: [6u8; 64], }], - flags: 0u8, value_balance: 1000i64, anchor: [7u8; 32], proof: vec![8u8; 100], diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/v0_methods.rs index e8b1714de4c..ce0d2c67e41 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/v0_methods.rs @@ -15,7 +15,6 @@ impl UnshieldTransitionMethodsV0 for UnshieldTransitionV0 { output_address: PlatformAddress, amount: u64, actions: Vec, - flags: u8, value_balance: i64, anchor: [u8; 32], proof: Vec, @@ -26,7 +25,6 @@ impl UnshieldTransitionMethodsV0 for UnshieldTransitionV0 { output_address, amount, actions, - flags, value_balance, anchor, proof, From 7016a813fcb5311a0c0f05259b5f1a4103961c3b Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 5 Mar 2026 00:10:22 +0700 Subject: [PATCH 08/30] fix(dpp): resolve clippy warnings in shielded builder - Allow too_many_arguments on 6 builder functions - Replace clone-to-slice with std::slice::from_ref for SpendAuthorizingKey - Fix doc_lazy_continuation in SerializedAction docs Co-Authored-By: Claude Opus 4.6 --- packages/rs-dpp/src/shielded/builder.rs | 10 ++++++++-- packages/rs-dpp/src/shielded/mod.rs | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/rs-dpp/src/shielded/builder.rs b/packages/rs-dpp/src/shielded/builder.rs index 6ef350474fb..ab31e885b6f 100644 --- a/packages/rs-dpp/src/shielded/builder.rs +++ b/packages/rs-dpp/src/shielded/builder.rs @@ -164,6 +164,7 @@ fn build_output_only_bundle( /// /// Used by ShieldedTransfer, Unshield, and ShieldedWithdrawal where funds /// are spent from existing notes. +#[allow(clippy::too_many_arguments)] fn build_spend_bundle( spends: Vec, recipient: &OrchardAddress, @@ -194,7 +195,7 @@ fn build_spend_bundle( ) .map_err(|e| ProtocolError::Generic(format!("failed to add output: {:?}", e)))?; - prove_and_sign_bundle(builder, proving_key, &[ask.clone()], extra_sighash_data) + prove_and_sign_bundle(builder, proving_key, std::slice::from_ref(ask), extra_sighash_data) } /// Takes a configured Builder, generates the proof, computes the platform @@ -243,6 +244,7 @@ fn prove_and_sign_bundle( /// - `proving_key` - Halo 2 proving key (cache with `OnceLock` — ~30s to build) /// - `memo` - 36-byte structured memo for the recipient (4-byte type tag + 32-byte payload) /// - `platform_version` - Protocol version +#[allow(clippy::too_many_arguments)] pub fn build_shield_transition>( recipient: &OrchardAddress, shield_amount: u64, @@ -291,6 +293,7 @@ pub fn build_shield_transition>( /// - `proving_key` - Halo 2 proving key /// - `memo` - 36-byte structured memo for the recipient (4-byte type tag + 32-byte payload) /// - `platform_version` - Protocol version +#[allow(clippy::too_many_arguments)] pub fn build_shield_from_asset_lock_transition( recipient: &OrchardAddress, shield_amount: u64, @@ -348,6 +351,7 @@ pub fn build_shield_from_asset_lock_transition( /// - `fee` - Optional fee override; if `None`, the minimum fee is computed automatically. /// If `Some`, must be >= the minimum fee. /// - `platform_version` - Protocol version +#[allow(clippy::too_many_arguments)] pub fn build_shielded_transfer_transition( spends: Vec, recipient: &OrchardAddress, @@ -424,7 +428,7 @@ pub fn build_shielded_transfer_transition( } // ShieldedTransfer has no extra_data in sighash - let bundle = prove_and_sign_bundle(builder, proving_key, &[ask.clone()], &[])?; + let bundle = prove_and_sign_bundle(builder, proving_key, std::slice::from_ref(ask), &[])?; let sb = serialize_authorized_bundle(&bundle); // value_balance = effective_fee (the amount leaving the shielded pool as fee) @@ -457,6 +461,7 @@ pub fn build_shielded_transfer_transition( /// - `fee` - Optional fee override; if `None`, the minimum fee is computed automatically. /// If `Some`, must be >= the minimum fee. /// - `platform_version` - Protocol version +#[allow(clippy::too_many_arguments)] pub fn build_unshield_transition( spends: Vec, output_address: PlatformAddress, @@ -549,6 +554,7 @@ pub fn build_unshield_transition( /// - `fee` - Optional fee override; if `None`, the minimum fee is computed automatically. /// If `Some`, must be >= the minimum fee. /// - `platform_version` - Protocol version +#[allow(clippy::too_many_arguments)] pub fn build_shielded_withdrawal_transition( spends: Vec, withdrawal_amount: u64, diff --git a/packages/rs-dpp/src/shielded/mod.rs b/packages/rs-dpp/src/shielded/mod.rs index 92d5f914cf4..5e50ecebeec 100644 --- a/packages/rs-dpp/src/shielded/mod.rs +++ b/packages/rs-dpp/src/shielded/mod.rs @@ -149,6 +149,7 @@ pub struct SerializedAction { /// - `epk`: ephemeral public key for Diffie-Hellman key agreement (32 bytes) /// - `enc_ciphertext`: note plaintext encrypted to the recipient (104 bytes = 52 compact + 36 memo + 16 AEAD tag) /// - `out_ciphertext`: encrypted to the sender for wallet recovery (80 bytes) + /// /// Stored on-chain so recipients can scan and decrypt notes addressed to them. /// Only the intended recipient (or sender) can decrypt; all others see random bytes. pub encrypted_note: Vec, From 4d8cc5979b13378a993f736d5fa0f4aabd9e063e Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 5 Mar 2026 07:19:06 +0700 Subject: [PATCH 09/30] refactor(dpp): rename shielded-bundle-building feature to shielded-tx Shorter, clearer name for the feature gate that enables OrchardAddress, the shielded transaction builder, and grovedb-commitment-tree dependency. Co-Authored-By: Claude Opus 4.6 --- packages/rs-dpp/Cargo.toml | 2 +- .../src/address_funds/platform_address.rs | 40 +++++++++---------- packages/rs-dpp/src/shielded/builder.rs | 2 +- packages/rs-dpp/src/shielded/mod.rs | 2 +- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/rs-dpp/Cargo.toml b/packages/rs-dpp/Cargo.toml index 0e439cb121f..78f36d959fc 100644 --- a/packages/rs-dpp/Cargo.toml +++ b/packages/rs-dpp/Cargo.toml @@ -328,7 +328,7 @@ extended-document = [ ] token-reward-explanations = ["dep:chrono-tz"] -shielded-bundle-building = [ +shielded-tx = [ "state-transition-signing", "dep:grovedb-commitment-tree", ] diff --git a/packages/rs-dpp/src/address_funds/platform_address.rs b/packages/rs-dpp/src/address_funds/platform_address.rs index 5fdcff12711..f893f2a4370 100644 --- a/packages/rs-dpp/src/address_funds/platform_address.rs +++ b/packages/rs-dpp/src/address_funds/platform_address.rs @@ -556,17 +556,17 @@ impl PlatformAddress { } // --------------------------------------------------------------------------- -// Orchard shielded payment address (requires `shielded-bundle-building`) +// Orchard shielded payment address (requires `shielded-tx`) // --------------------------------------------------------------------------- /// Size of the Orchard diversifier (11 bytes). -#[cfg(feature = "shielded-bundle-building")] +#[cfg(feature = "shielded-tx")] pub const ORCHARD_DIVERSIFIER_SIZE: usize = 11; /// Size of the Orchard diversified transmission key pk_d (32 bytes, Pallas curve point). -#[cfg(feature = "shielded-bundle-building")] +#[cfg(feature = "shielded-tx")] pub const ORCHARD_PKD_SIZE: usize = 32; /// Total size of a raw Orchard payment address (43 bytes = diversifier + pk_d). -#[cfg(feature = "shielded-bundle-building")] +#[cfg(feature = "shielded-tx")] pub const ORCHARD_ADDRESS_SIZE: usize = ORCHARD_DIVERSIFIER_SIZE + ORCHARD_PKD_SIZE; /// An Orchard shielded payment address. @@ -587,12 +587,12 @@ pub const ORCHARD_ADDRESS_SIZE: usize = ORCHARD_DIVERSIFIER_SIZE + ORCHARD_PKD_S /// to convert from the orchard crate's native type, or [`inner()`](OrchardAddress::inner) /// / [`into_inner()`](OrchardAddress::into_inner) to access the wrapped address. /// -/// Requires the `shielded-bundle-building` feature. -#[cfg(feature = "shielded-bundle-building")] +/// Requires the `shielded-tx` feature. +#[cfg(feature = "shielded-tx")] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct OrchardAddress(grovedb_commitment_tree::PaymentAddress); -#[cfg(feature = "shielded-bundle-building")] +#[cfg(feature = "shielded-tx")] impl OrchardAddress { /// Type byte for Orchard addresses in bech32m encoding (user-facing). /// Produces 'z' as the first bech32 character. @@ -691,7 +691,7 @@ impl OrchardAddress { } /// Infallible conversion from the orchard crate's `PaymentAddress` to `OrchardAddress`. -#[cfg(feature = "shielded-bundle-building")] +#[cfg(feature = "shielded-tx")] impl From for OrchardAddress { fn from(addr: grovedb_commitment_tree::PaymentAddress) -> Self { Self(addr) @@ -699,14 +699,14 @@ impl From for OrchardAddress { } /// Infallible conversion from a reference to `PaymentAddress`. -#[cfg(feature = "shielded-bundle-building")] +#[cfg(feature = "shielded-tx")] impl From<&grovedb_commitment_tree::PaymentAddress> for OrchardAddress { fn from(addr: &grovedb_commitment_tree::PaymentAddress) -> Self { Self(*addr) } } -#[cfg(feature = "shielded-bundle-building")] +#[cfg(feature = "shielded-tx")] impl std::fmt::Display for OrchardAddress { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let raw = self.to_raw_bytes(); @@ -1479,10 +1479,10 @@ mod tests { } // ======================== - // Orchard address tests (require shielded-bundle-building feature) + // Orchard address tests (require shielded-tx feature) // ======================== - #[cfg(feature = "shielded-bundle-building")] + #[cfg(feature = "shielded-tx")] fn test_orchard_address() -> OrchardAddress { use grovedb_commitment_tree::{FullViewingKey, Scope, SpendingKey}; let sk = SpendingKey::from_bytes([42u8; 32]).unwrap(); @@ -1491,7 +1491,7 @@ mod tests { OrchardAddress::from(payment_address) } - #[cfg(feature = "shielded-bundle-building")] + #[cfg(feature = "shielded-tx")] #[test] fn test_orchard_address_raw_bytes_roundtrip() { let address = test_orchard_address(); @@ -1502,7 +1502,7 @@ mod tests { assert_eq!(recovered, address); } - #[cfg(feature = "shielded-bundle-building")] + #[cfg(feature = "shielded-tx")] #[test] fn test_orchard_bech32m_mainnet_roundtrip() { let address = test_orchard_address(); @@ -1520,7 +1520,7 @@ mod tests { assert_eq!(network, Network::Dash); } - #[cfg(feature = "shielded-bundle-building")] + #[cfg(feature = "shielded-tx")] #[test] fn test_orchard_bech32m_testnet_roundtrip() { let address = test_orchard_address(); @@ -1538,7 +1538,7 @@ mod tests { assert_eq!(network, Network::Testnet); } - #[cfg(feature = "shielded-bundle-building")] + #[cfg(feature = "shielded-tx")] #[test] fn test_orchard_bech32m_wrong_type_byte_fails() { // Manually construct an address with P2PKH type byte (0xb0) but 44-byte payload @@ -1555,7 +1555,7 @@ mod tests { .contains("invalid Orchard address type byte")); } - #[cfg(feature = "shielded-bundle-building")] + #[cfg(feature = "shielded-tx")] #[test] fn test_orchard_bech32m_wrong_length_fails() { // Too short (only 20 bytes instead of 43) @@ -1572,7 +1572,7 @@ mod tests { .contains("invalid Orchard address length")); } - #[cfg(feature = "shielded-bundle-building")] + #[cfg(feature = "shielded-tx")] #[test] fn test_orchard_and_platform_addresses_are_distinguishable() { let p2pkh = PlatformAddress::P2pkh([0xAB; 20]); @@ -1597,7 +1597,7 @@ mod tests { assert!(OrchardAddress::from_bech32m_string(&p2pkh_enc).is_err()); } - #[cfg(feature = "shielded-bundle-building")] + #[cfg(feature = "shielded-tx")] #[test] fn test_orchard_address_from_raw_bytes_invalid_pk_d() { // All zeros for pk_d is not a valid Pallas curve point @@ -1606,7 +1606,7 @@ mod tests { assert!(OrchardAddress::from_raw_bytes(&raw).is_err()); } - #[cfg(feature = "shielded-bundle-building")] + #[cfg(feature = "shielded-tx")] #[test] fn test_orchard_address_display() { let address = test_orchard_address(); diff --git a/packages/rs-dpp/src/shielded/builder.rs b/packages/rs-dpp/src/shielded/builder.rs index ab31e885b6f..5a2513b605c 100644 --- a/packages/rs-dpp/src/shielded/builder.rs +++ b/packages/rs-dpp/src/shielded/builder.rs @@ -4,7 +4,7 @@ //! builder configuration, proof generation, signature application, //! and serialization into platform state transitions. //! -//! Requires the `shielded-bundle-building` feature, which pulls in +//! Requires the `shielded-tx` feature, which pulls in //! `grovedb-commitment-tree` (and transitively the `orchard` crate). //! //! # Example diff --git a/packages/rs-dpp/src/shielded/mod.rs b/packages/rs-dpp/src/shielded/mod.rs index 5e50ecebeec..0ad06685494 100644 --- a/packages/rs-dpp/src/shielded/mod.rs +++ b/packages/rs-dpp/src/shielded/mod.rs @@ -1,4 +1,4 @@ -#[cfg(feature = "shielded-bundle-building")] +#[cfg(feature = "shielded-tx")] pub mod builder; use bincode::{Decode, Encode}; From ecad334c0e905570874516abf9384ac956f513df Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 5 Mar 2026 07:21:20 +0700 Subject: [PATCH 10/30] refactor(dpp): remove flags from OrchardBundleParams Flags are deterministic per transition type, same as the transition structs themselves. Co-Authored-By: Claude Opus 4.6 --- packages/rs-dpp/src/shielded/mod.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/rs-dpp/src/shielded/mod.rs b/packages/rs-dpp/src/shielded/mod.rs index 0ad06685494..c16f6bfd70c 100644 --- a/packages/rs-dpp/src/shielded/mod.rs +++ b/packages/rs-dpp/src/shielded/mod.rs @@ -83,14 +83,12 @@ pub(crate) mod serde_bytes_64 { /// Common Orchard bundle parameters shared across all shielded transition types. /// /// Groups the fields that every shielded transition carries identically: -/// the serialized actions, bundle flags, Sinsemilla anchor, Halo 2 proof, -/// and RedPallas binding signature. Using this struct reduces parameter counts -/// in SDK helper functions from 10-12 down to 5-8. +/// the serialized actions, Sinsemilla anchor, Halo 2 proof, and RedPallas +/// binding signature. Using this struct reduces parameter counts in SDK +/// helper functions from 10-12 down to 5-8. pub struct OrchardBundleParams { /// The serialized Orchard actions (spends + outputs). pub actions: Vec, - /// Bundle flags byte. - pub flags: u8, /// Sinsemilla root of the note commitment tree at bundle creation time (32 bytes). /// This is the Orchard Anchor — the root of the depth-32 Sinsemilla Merkle /// tree over extracted note commitments (cmx values), NOT the GroveDB From 36e95f5a9d5c03513a876d25e56ffbd07b13c277 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 5 Mar 2026 07:27:20 +0700 Subject: [PATCH 11/30] chore(dpp): add TODO to remove user_fee_increase from ShieldFromAssetLock The fee is implicitly the difference between the asset lock value and value_balance, so no separate fee multiplier is needed. Co-Authored-By: Claude Opus 4.6 --- .../shielded/shield_from_asset_lock_transition/v0/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs index bbb4787dc62..fc352d4a054 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs @@ -49,6 +49,8 @@ pub struct ShieldFromAssetLockTransitionV0 { serde(with = "crate::shielded::serde_bytes_64") )] pub binding_signature: [u8; 64], + // TODO: remove user_fee_increase — the fee is implicitly the difference between + // the asset lock value and value_balance, so no separate fee multiplier is needed. /// Fee multiplier pub user_fee_increase: UserFeeIncrease, /// ECDSA signature over the signable bytes (excluded from sig hash) From 413b4e53bf70ed658f4e40afb44cf856cc7979c8 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 5 Mar 2026 10:07:55 +0700 Subject: [PATCH 12/30] refactor(dpp): apply user_fee_increase trait extraction to shielded transitions Remove StateTransitionHasUserFeeIncrease from shielded transitions that don't support fee adjustment (ShieldedTransfer, Unshield, ShieldedWithdrawal, ShieldFromAssetLock) and move it to the new trait for Shield transition. Co-Authored-By: Claude Opus 4.6 --- packages/rs-dpp/src/shielded/builder.rs | 7 ++++++- packages/rs-dpp/src/state_transition/mod.rs | 10 ++++++++++ .../state_transition_like.rs | 17 ----------------- .../v0/state_transition_like.rs | 9 --------- .../shield_transition/state_transition_like.rs | 15 +++++++++------ .../v0/state_transition_like.rs | 3 +++ .../state_transition_like.rs | 10 ---------- .../v0/state_transition_like.rs | 9 --------- .../state_transition_like.rs | 10 ---------- .../v0/state_transition_like.rs | 10 +--------- .../state_transition_like.rs | 10 ---------- .../v0/state_transition_like.rs | 10 +--------- 12 files changed, 30 insertions(+), 90 deletions(-) diff --git a/packages/rs-dpp/src/shielded/builder.rs b/packages/rs-dpp/src/shielded/builder.rs index 5a2513b605c..00d9a632f4a 100644 --- a/packages/rs-dpp/src/shielded/builder.rs +++ b/packages/rs-dpp/src/shielded/builder.rs @@ -195,7 +195,12 @@ fn build_spend_bundle( ) .map_err(|e| ProtocolError::Generic(format!("failed to add output: {:?}", e)))?; - prove_and_sign_bundle(builder, proving_key, std::slice::from_ref(ask), extra_sighash_data) + prove_and_sign_bundle( + builder, + proving_key, + std::slice::from_ref(ask), + extra_sighash_data, + ) } /// Takes a configured Builder, generates the proof, computes the platform diff --git a/packages/rs-dpp/src/state_transition/mod.rs b/packages/rs-dpp/src/state_transition/mod.rs index cb088f216dd..d6097b50427 100644 --- a/packages/rs-dpp/src/state_transition/mod.rs +++ b/packages/rs-dpp/src/state_transition/mod.rs @@ -716,8 +716,13 @@ impl StateTransition { StateTransition::AddressFundsTransfer(st) => st.user_fee_increase(), StateTransition::AddressFundingFromAssetLock(st) => st.user_fee_increase(), StateTransition::AddressCreditWithdrawal(st) => st.user_fee_increase(), + StateTransition::Shield(st) => st.user_fee_increase(), // These transitions don't support user fee adjustment + StateTransition::ShieldFromAssetLock(_) => 0, StateTransition::MasternodeVote(_) => 0, + StateTransition::ShieldedTransfer(_) => 0, + StateTransition::Unshield(_) => 0, + StateTransition::ShieldedWithdrawal(_) => 0, } } @@ -919,8 +924,13 @@ impl StateTransition { StateTransition::AddressCreditWithdrawal(st) => { st.set_user_fee_increase(user_fee_increase) } + StateTransition::Shield(st) => st.set_user_fee_increase(user_fee_increase), // These transitions don't support user fee adjustment — no-op + StateTransition::ShieldFromAssetLock(_) => {} StateTransition::MasternodeVote(_) => {} + StateTransition::ShieldedTransfer(_) => {} + StateTransition::Unshield(_) => {} + StateTransition::ShieldedWithdrawal(_) => {} } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/state_transition_like.rs index 51844628ca7..be94599a6ad 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/state_transition_like.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/state_transition_like.rs @@ -1,4 +1,3 @@ -use crate::prelude::UserFeeIncrease; use crate::state_transition::shield_from_asset_lock_transition::ShieldFromAssetLockTransition; use crate::state_transition::{ StateTransitionLike, StateTransitionSingleSigned, StateTransitionType, @@ -27,22 +26,6 @@ impl StateTransitionLike for ShieldFromAssetLockTransition { } } - /// returns the fee multiplier - fn user_fee_increase(&self) -> UserFeeIncrease { - match self { - ShieldFromAssetLockTransition::V0(transition) => transition.user_fee_increase(), - } - } - - /// set a fee multiplier - fn set_user_fee_increase(&mut self, user_fee_increase: UserFeeIncrease) { - match self { - ShieldFromAssetLockTransition::V0(transition) => { - transition.set_user_fee_increase(user_fee_increase) - } - } - } - fn unique_identifiers(&self) -> Vec { match self { ShieldFromAssetLockTransition::V0(transition) => transition.unique_identifiers(), diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/state_transition_like.rs index 6314d80b308..19727081167 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/state_transition_like.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/state_transition_like.rs @@ -1,6 +1,5 @@ use platform_value::BinaryData; -use crate::prelude::UserFeeIncrease; use crate::state_transition::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0; use crate::state_transition::shield_from_asset_lock_transition::ShieldFromAssetLockTransition; use crate::state_transition::{StateTransition, StateTransitionSingleSigned}; @@ -36,14 +35,6 @@ impl StateTransitionLike for ShieldFromAssetLockTransitionV0 { fn unique_identifiers(&self) -> Vec { self.actions.iter().map(|a| hex::encode(a.cmx)).collect() } - - fn user_fee_increase(&self) -> UserFeeIncrease { - self.user_fee_increase - } - - fn set_user_fee_increase(&mut self, user_fee_increase: UserFeeIncrease) { - self.user_fee_increase = user_fee_increase - } } impl StateTransitionSingleSigned for ShieldFromAssetLockTransitionV0 { diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/state_transition_like.rs index 089ae4b2749..017a143bc2e 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/state_transition_like.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/state_transition_like.rs @@ -1,6 +1,7 @@ use crate::address_funds::AddressWitness; use crate::prelude::UserFeeIncrease; use crate::state_transition::shield_transition::ShieldTransition; +use crate::state_transition::StateTransitionHasUserFeeIncrease; use crate::state_transition::{ StateTransitionLike, StateTransitionType, StateTransitionWitnessSigned, }; @@ -27,6 +28,14 @@ impl StateTransitionLike for ShieldTransition { } } + fn unique_identifiers(&self) -> Vec { + match self { + ShieldTransition::V0(transition) => transition.unique_identifiers(), + } + } +} + +impl StateTransitionHasUserFeeIncrease for ShieldTransition { /// returns the fee multiplier fn user_fee_increase(&self) -> UserFeeIncrease { match self { @@ -39,12 +48,6 @@ impl StateTransitionLike for ShieldTransition { ShieldTransition::V0(transition) => transition.set_user_fee_increase(user_fee_increase), } } - - fn unique_identifiers(&self) -> Vec { - match self { - ShieldTransition::V0(transition) => transition.unique_identifiers(), - } - } } impl StateTransitionWitnessSigned for ShieldTransition { diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/state_transition_like.rs index 2fbe21a3f39..a2ee4c0ac43 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/state_transition_like.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/state_transition_like.rs @@ -2,6 +2,7 @@ use crate::address_funds::AddressWitness; use crate::prelude::UserFeeIncrease; use crate::state_transition::shield_transition::v0::ShieldTransitionV0; use crate::state_transition::shield_transition::ShieldTransition; +use crate::state_transition::StateTransitionHasUserFeeIncrease; use crate::{ prelude::Identifier, state_transition::{StateTransitionLike, StateTransitionType}, @@ -40,7 +41,9 @@ impl StateTransitionLike for ShieldTransitionV0 { .map(|(key, (nonce, _))| key.base64_string_with_nonce(*nonce)) .collect() } +} +impl StateTransitionHasUserFeeIncrease for ShieldTransitionV0 { fn user_fee_increase(&self) -> UserFeeIncrease { self.user_fee_increase } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/state_transition_like.rs index a6f2fa9fa74..6ba20092d1a 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/state_transition_like.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/state_transition_like.rs @@ -1,4 +1,3 @@ -use crate::prelude::UserFeeIncrease; use crate::state_transition::shielded_transfer_transition::ShieldedTransferTransition; use crate::state_transition::{StateTransitionLike, StateTransitionType}; use crate::version::FeatureVersion; @@ -24,15 +23,6 @@ impl StateTransitionLike for ShieldedTransferTransition { } } - /// returns the fee multiplier - fn user_fee_increase(&self) -> UserFeeIncrease { - 0 - } - /// set a fee multiplier - fn set_user_fee_increase(&mut self, _user_fee_increase: UserFeeIncrease) { - // No-op: fee is cryptographically locked by the Orchard binding signature - } - fn unique_identifiers(&self) -> Vec { match self { ShieldedTransferTransition::V0(transition) => transition.unique_identifiers(), diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/state_transition_like.rs index 8e6088c3bf5..8163b81b8ce 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/state_transition_like.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/state_transition_like.rs @@ -1,4 +1,3 @@ -use crate::prelude::UserFeeIncrease; use crate::state_transition::shielded_transfer_transition::v0::ShieldedTransferTransitionV0; use crate::state_transition::shielded_transfer_transition::ShieldedTransferTransition; use crate::{ @@ -40,12 +39,4 @@ impl StateTransitionLike for ShieldedTransferTransitionV0 { .map(|action| hex::encode(action.nullifier)) .collect() } - - fn user_fee_increase(&self) -> UserFeeIncrease { - 0 - } - - fn set_user_fee_increase(&mut self, _user_fee_increase: UserFeeIncrease) { - // No-op: fee is cryptographically locked by the Orchard binding signature - } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/state_transition_like.rs index fda27be8a1c..699d0e7a1ea 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/state_transition_like.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/state_transition_like.rs @@ -1,4 +1,3 @@ -use crate::prelude::UserFeeIncrease; use crate::state_transition::shielded_withdrawal_transition::ShieldedWithdrawalTransition; use crate::state_transition::{StateTransitionLike, StateTransitionType}; use crate::version::FeatureVersion; @@ -24,15 +23,6 @@ impl StateTransitionLike for ShieldedWithdrawalTransition { } } - /// returns the fee multiplier - fn user_fee_increase(&self) -> UserFeeIncrease { - 0 - } - /// set a fee multiplier - fn set_user_fee_increase(&mut self, _user_fee_increase: UserFeeIncrease) { - // No-op: shielded withdrawal fees are cryptographically locked by the Orchard binding signature - } - fn unique_identifiers(&self) -> Vec { match self { ShieldedWithdrawalTransition::V0(transition) => transition.unique_identifiers(), diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/state_transition_like.rs index 3f53fe84490..77cd8ccbb05 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/state_transition_like.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/state_transition_like.rs @@ -1,7 +1,7 @@ use crate::state_transition::shielded_withdrawal_transition::v0::ShieldedWithdrawalTransitionV0; use crate::state_transition::shielded_withdrawal_transition::ShieldedWithdrawalTransition; use crate::{ - prelude::{Identifier, UserFeeIncrease}, + prelude::Identifier, state_transition::{StateTransitionLike, StateTransitionType}, }; @@ -39,12 +39,4 @@ impl StateTransitionLike for ShieldedWithdrawalTransitionV0 { .map(|action| hex::encode(action.nullifier)) .collect() } - - fn user_fee_increase(&self) -> UserFeeIncrease { - 0 - } - - fn set_user_fee_increase(&mut self, _user_fee_increase: UserFeeIncrease) { - // No-op: shielded withdrawal fees are cryptographically locked by the Orchard binding signature - } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/state_transition_like.rs index 658cd5aeb7a..4b5e674f5ec 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/state_transition_like.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/state_transition_like.rs @@ -1,4 +1,3 @@ -use crate::prelude::UserFeeIncrease; use crate::state_transition::unshield_transition::UnshieldTransition; use crate::state_transition::{StateTransitionLike, StateTransitionType}; use crate::version::FeatureVersion; @@ -24,15 +23,6 @@ impl StateTransitionLike for UnshieldTransition { } } - /// returns the fee multiplier - fn user_fee_increase(&self) -> UserFeeIncrease { - 0 - } - /// set a fee multiplier - fn set_user_fee_increase(&mut self, _user_fee_increase: UserFeeIncrease) { - // No-op: fee is cryptographically locked by the Orchard binding signature - } - fn unique_identifiers(&self) -> Vec { match self { UnshieldTransition::V0(transition) => transition.unique_identifiers(), diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/state_transition_like.rs index a4120d242bb..1bce7f2e56e 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/state_transition_like.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/state_transition_like.rs @@ -1,7 +1,7 @@ use crate::state_transition::unshield_transition::v0::UnshieldTransitionV0; use crate::state_transition::unshield_transition::UnshieldTransition; use crate::{ - prelude::{Identifier, UserFeeIncrease}, + prelude::Identifier, state_transition::{StateTransitionLike, StateTransitionType}, }; @@ -39,12 +39,4 @@ impl StateTransitionLike for UnshieldTransitionV0 { .map(|action| hex::encode(action.nullifier)) .collect() } - - fn user_fee_increase(&self) -> UserFeeIncrease { - 0 - } - - fn set_user_fee_increase(&mut self, _user_fee_increase: UserFeeIncrease) { - // No-op: fee is cryptographically locked by the Orchard binding signature - } } From 114675b7be4bfe72732a93b44c7f6912131e612a Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 5 Mar 2026 13:00:47 +0700 Subject: [PATCH 13/30] refactor(dpp): split shielded builder into per-function files with tests Split the monolithic builder/mod.rs (639 lines) into individual files per builder function, keeping shared types and internal helpers in mod.rs. Added 9 tests covering error paths and happy paths for all 5 builders. Co-Authored-By: Claude Opus 4.6 --- packages/rs-dpp/src/shielded/builder.rs | 638 ------------------ packages/rs-dpp/src/shielded/builder/mod.rs | 277 ++++++++ .../rs-dpp/src/shielded/builder/shield.rs | 159 +++++ .../builder/shield_from_asset_lock.rs | 99 +++ .../src/shielded/builder/shielded_transfer.rs | 208 ++++++ .../shielded/builder/shielded_withdrawal.rs | 197 ++++++ .../rs-dpp/src/shielded/builder/unshield.rs | 185 +++++ 7 files changed, 1125 insertions(+), 638 deletions(-) delete mode 100644 packages/rs-dpp/src/shielded/builder.rs create mode 100644 packages/rs-dpp/src/shielded/builder/mod.rs create mode 100644 packages/rs-dpp/src/shielded/builder/shield.rs create mode 100644 packages/rs-dpp/src/shielded/builder/shield_from_asset_lock.rs create mode 100644 packages/rs-dpp/src/shielded/builder/shielded_transfer.rs create mode 100644 packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs create mode 100644 packages/rs-dpp/src/shielded/builder/unshield.rs diff --git a/packages/rs-dpp/src/shielded/builder.rs b/packages/rs-dpp/src/shielded/builder.rs deleted file mode 100644 index 00d9a632f4a..00000000000 --- a/packages/rs-dpp/src/shielded/builder.rs +++ /dev/null @@ -1,638 +0,0 @@ -//! Convenience builders for constructing shielded state transitions. -//! -//! These functions encapsulate the full Orchard bundle construction pipeline: -//! builder configuration, proof generation, signature application, -//! and serialization into platform state transitions. -//! -//! Requires the `shielded-tx` feature, which pulls in -//! `grovedb-commitment-tree` (and transitively the `orchard` crate). -//! -//! # Example -//! -//! ```ignore -//! use dpp::shielded::builder::*; -//! use grovedb_commitment_tree::{SpendingKey, FullViewingKey, Scope, ProvingKey}; -//! -//! // Derive recipient address -//! let sk = SpendingKey::from_bytes(seed)?; -//! let fvk = FullViewingKey::from(&sk); -//! let recipient = OrchardAddress::from_raw_bytes( -//! &fvk.address_at(0, Scope::External).to_raw_address_bytes(), -//! ); -//! -//! // Build a shield transition -//! let pk = ProvingKey::build(); -//! let st = build_shield_transition( -//! &recipient, shield_amount, inputs, fee_strategy, -//! &signer, 0, &pk, [0u8; 36], platform_version, -//! )?; -//! ``` - -use std::collections::BTreeMap; - -use grovedb_commitment_tree::{ - Anchor, Authorized, Builder, Bundle, BundleType, DashMemo, Flags as OrchardFlags, - FullViewingKey, MerklePath, Note, NoteValue, PaymentAddress, ProvingKey, SpendAuthorizingKey, -}; -use rand::rngs::OsRng; - -use crate::address_funds::AddressFundsFeeStrategy; -use crate::address_funds::{OrchardAddress, PlatformAddress}; -use crate::fee::Credits; -use crate::identity::core_script::CoreScript; -use crate::identity::signer::Signer; -use crate::prelude::{AddressNonce, UserFeeIncrease}; -use crate::shielded::{compute_minimum_shielded_fee, compute_platform_sighash, SerializedAction}; -use crate::state_transition::shield_from_asset_lock_transition::methods::ShieldFromAssetLockTransitionMethodsV0; -use crate::state_transition::shield_from_asset_lock_transition::ShieldFromAssetLockTransition; -use crate::state_transition::shield_transition::methods::ShieldTransitionMethodsV0; -use crate::state_transition::shield_transition::ShieldTransition; -use crate::state_transition::shielded_transfer_transition::methods::ShieldedTransferTransitionMethodsV0; -use crate::state_transition::shielded_transfer_transition::ShieldedTransferTransition; -use crate::state_transition::shielded_withdrawal_transition::methods::ShieldedWithdrawalTransitionMethodsV0; -use crate::state_transition::shielded_withdrawal_transition::ShieldedWithdrawalTransition; -use crate::state_transition::unshield_transition::methods::UnshieldTransitionMethodsV0; -use crate::state_transition::unshield_transition::UnshieldTransition; -use crate::state_transition::StateTransition; -use crate::withdrawal::Pooling; -use crate::ProtocolError; -use platform_version::version::PlatformVersion; - -/// A note that can be spent in a shielded transaction, paired with its -/// Merkle inclusion path in the commitment tree. -pub struct SpendableNote { - /// The Orchard note to spend. - pub note: Note, - /// Merkle path proving the note's commitment exists in the tree. - pub merkle_path: MerklePath, -} - -/// The serialized fields extracted from an authorized Orchard bundle, -/// ready for use by state transition constructors. -pub struct SerializedBundle { - /// Serialized Orchard actions (spends + outputs). - pub actions: Vec, - /// Bundle flags byte. - pub flags: u8, - /// Net value balance (positive = value leaving the shielded pool). - pub value_balance: i64, - /// Sinsemilla root of the Orchard note commitment tree (32 bytes). - /// This is the Orchard `Anchor` — the root hash of the depth-32 Sinsemilla - /// Merkle tree over extracted note commitments (cmx values). - pub anchor: [u8; 32], - /// Halo 2 proof bytes. - pub proof: Vec, - /// Binding signature (64 bytes). - pub binding_signature: [u8; 64], -} - -impl From<&OrchardAddress> for PaymentAddress { - fn from(address: &OrchardAddress) -> Self { - *address.inner() - } -} - -/// Serializes an authorized Orchard bundle into the raw fields used by -/// state transition constructors. -pub fn serialize_authorized_bundle(bundle: &Bundle) -> SerializedBundle { - let actions: Vec = bundle - .actions() - .iter() - .map(|action| { - let enc = action.encrypted_note(); - let mut encrypted_note = Vec::with_capacity(216); - encrypted_note.extend_from_slice(&enc.epk_bytes); - encrypted_note.extend_from_slice(enc.enc_ciphertext.as_ref()); - encrypted_note.extend_from_slice(&enc.out_ciphertext); - SerializedAction { - nullifier: action.nullifier().to_bytes(), - rk: <[u8; 32]>::from(action.rk()), - cmx: action.cmx().to_bytes(), - encrypted_note, - cv_net: action.cv_net().to_bytes(), - spend_auth_sig: <[u8; 64]>::from(action.authorization()), - } - }) - .collect(); - let flags = bundle.flags().to_byte(); - let value_balance = *bundle.value_balance(); - let anchor = bundle.anchor().to_bytes(); - let proof = bundle.authorization().proof().as_ref().to_vec(); - let binding_signature = <[u8; 64]>::from(bundle.authorization().binding_signature()); - SerializedBundle { - actions, - flags, - value_balance, - anchor, - proof, - binding_signature, - } -} - -// --------------------------------------------------------------------------- -// Internal helpers -// --------------------------------------------------------------------------- - -/// Builds an output-only Orchard bundle (no spends). -/// -/// Used by Shield and ShieldFromAssetLock transitions where funds enter -/// the shielded pool from transparent sources. -fn build_output_only_bundle( - recipient: &OrchardAddress, - amount: u64, - memo: [u8; 36], - proving_key: &ProvingKey, -) -> Result, ProtocolError> { - let payment_address = PaymentAddress::from(recipient); - let anchor = Anchor::empty_tree(); - let mut builder = Builder::::new( - BundleType::Transactional { - flags: OrchardFlags::SPENDS_DISABLED, - bundle_required: false, - }, - anchor, - ); - - builder - .add_output(None, payment_address, NoteValue::from_raw(amount), memo) - .map_err(|e| ProtocolError::Generic(format!("failed to add output: {:?}", e)))?; - - prove_and_sign_bundle(builder, proving_key, &[], &[]) -} - -/// Builds a spend+output Orchard bundle. -/// -/// Used by ShieldedTransfer, Unshield, and ShieldedWithdrawal where funds -/// are spent from existing notes. -#[allow(clippy::too_many_arguments)] -fn build_spend_bundle( - spends: Vec, - recipient: &OrchardAddress, - output_amount: u64, - memo: [u8; 36], - fvk: &FullViewingKey, - ask: &SpendAuthorizingKey, - anchor: Anchor, - proving_key: &ProvingKey, - extra_sighash_data: &[u8], -) -> Result, ProtocolError> { - let payment_address = PaymentAddress::from(recipient); - - let mut builder = Builder::::new(BundleType::DEFAULT, anchor); - - for spend in spends { - builder - .add_spend(fvk.clone(), spend.note, spend.merkle_path) - .map_err(|e| ProtocolError::Generic(format!("failed to add spend: {:?}", e)))?; - } - - builder - .add_output( - None, - payment_address, - NoteValue::from_raw(output_amount), - memo, - ) - .map_err(|e| ProtocolError::Generic(format!("failed to add output: {:?}", e)))?; - - prove_and_sign_bundle( - builder, - proving_key, - std::slice::from_ref(ask), - extra_sighash_data, - ) -} - -/// Takes a configured Builder, generates the proof, computes the platform -/// sighash, and applies signatures. -fn prove_and_sign_bundle( - builder: Builder, - proving_key: &ProvingKey, - signing_keys: &[SpendAuthorizingKey], - extra_sighash_data: &[u8], -) -> Result, ProtocolError> { - let mut rng = OsRng; - - let (unauthorized, _) = builder - .build::(&mut rng) - .map_err(|e| ProtocolError::Generic(format!("failed to build bundle: {:?}", e)))? - .ok_or_else(|| ProtocolError::Generic("bundle was empty after build".to_string()))?; - - let bundle_commitment: [u8; 32] = unauthorized.commitment().into(); - let sighash = compute_platform_sighash(&bundle_commitment, extra_sighash_data); - - let proven = unauthorized - .create_proof(proving_key, &mut rng) - .map_err(|e| ProtocolError::Generic(format!("failed to create proof: {:?}", e)))?; - - proven - .apply_signatures(rng, sighash, signing_keys) - .map_err(|e| ProtocolError::Generic(format!("failed to apply signatures: {:?}", e))) -} - -// --------------------------------------------------------------------------- -// Public builder functions -// --------------------------------------------------------------------------- - -/// Builds a Shield state transition (transparent platform addresses -> shielded pool). -/// -/// Constructs an output-only Orchard bundle (no spends), proves it, signs the -/// transparent input witnesses, and returns a ready-to-broadcast `StateTransition`. -/// -/// # Parameters -/// - `recipient` - Orchard address to receive the shielded note -/// - `shield_amount` - Amount of credits to shield -/// - `inputs` - Platform address inputs with their nonces and balances -/// - `fee_strategy` - How to deduct fees from the transparent inputs -/// - `signer` - Signs each input address witness (ECDSA) -/// - `user_fee_increase` - Fee multiplier (0 = 100% base fee) -/// - `proving_key` - Halo 2 proving key (cache with `OnceLock` — ~30s to build) -/// - `memo` - 36-byte structured memo for the recipient (4-byte type tag + 32-byte payload) -/// - `platform_version` - Protocol version -#[allow(clippy::too_many_arguments)] -pub fn build_shield_transition>( - recipient: &OrchardAddress, - shield_amount: u64, - inputs: BTreeMap, - fee_strategy: AddressFundsFeeStrategy, - signer: &S, - user_fee_increase: UserFeeIncrease, - proving_key: &ProvingKey, - memo: [u8; 36], - platform_version: &PlatformVersion, -) -> Result { - if fee_strategy.is_empty() { - return Err(ProtocolError::Generic( - "fee_strategy must have at least one step".to_string(), - )); - } - - let bundle = build_output_only_bundle(recipient, shield_amount, memo, proving_key)?; - let sb = serialize_authorized_bundle(&bundle); - - ShieldTransition::try_from_bundle_with_signer( - inputs, - sb.actions, - sb.value_balance, - sb.anchor, - sb.proof, - sb.binding_signature, - fee_strategy, - signer, - user_fee_increase, - platform_version, - ) -} - -/// Builds a ShieldFromAssetLock state transition (core asset lock -> shielded pool). -/// -/// Like Shield, constructs an output-only Orchard bundle. The funds come from -/// a core asset lock proof rather than platform address inputs. -/// -/// # Parameters -/// - `recipient` - Orchard address to receive the shielded note -/// - `shield_amount` - Amount of credits to shield (from the asset lock) -/// - `asset_lock_proof` - Proof that funds are locked on core chain -/// - `asset_lock_private_key` - Private key for the asset lock (signs the transition) -/// - `user_fee_increase` - Fee multiplier (0 = 100% base fee) -/// - `proving_key` - Halo 2 proving key -/// - `memo` - 36-byte structured memo for the recipient (4-byte type tag + 32-byte payload) -/// - `platform_version` - Protocol version -#[allow(clippy::too_many_arguments)] -pub fn build_shield_from_asset_lock_transition( - recipient: &OrchardAddress, - shield_amount: u64, - asset_lock_proof: crate::prelude::AssetLockProof, - asset_lock_private_key: &[u8], - user_fee_increase: UserFeeIncrease, - proving_key: &ProvingKey, - memo: [u8; 36], - platform_version: &PlatformVersion, -) -> Result { - let bundle = build_output_only_bundle(recipient, shield_amount, memo, proving_key)?; - let sb = serialize_authorized_bundle(&bundle); - - // For output-only bundles, Orchard value_balance is negative (value flowing in). - // Convert to u64 (absolute amount entering the pool). - let value_balance = sb - .value_balance - .checked_neg() - .and_then(|v| u64::try_from(v).ok()) - .ok_or_else(|| { - ProtocolError::Generic( - "shield_from_asset_lock: bundle value_balance is not negative".to_string(), - ) - })?; - - ShieldFromAssetLockTransition::try_from_asset_lock_with_bundle( - asset_lock_proof, - asset_lock_private_key, - sb.actions, - value_balance, - sb.anchor, - sb.proof, - sb.binding_signature, - user_fee_increase, - platform_version, - ) -} - -/// Builds a ShieldedTransfer state transition (shielded pool -> shielded pool). -/// -/// Spends existing notes and creates a new note for the recipient. The shielded -/// fee is deducted from the spent notes. Any remaining change is returned to -/// the `change_address`. -/// -/// # Parameters -/// - `spends` - Notes to spend with their Merkle paths -/// - `recipient` - Orchard address to receive the transferred note -/// - `transfer_amount` - Amount to transfer to the recipient -/// - `change_address` - Orchard address for change output (if any) -/// - `fvk` - Full viewing key for spend authorization -/// - `ask` - Spend authorizing key for RedPallas signatures -/// - `anchor` - Sinsemilla root of the note commitment tree (Orchard Anchor) -/// - `proving_key` - Halo 2 proving key -/// - `memo` - 36-byte structured memo for the recipient (4-byte type tag + 32-byte payload) -/// - `fee` - Optional fee override; if `None`, the minimum fee is computed automatically. -/// If `Some`, must be >= the minimum fee. -/// - `platform_version` - Protocol version -#[allow(clippy::too_many_arguments)] -pub fn build_shielded_transfer_transition( - spends: Vec, - recipient: &OrchardAddress, - transfer_amount: u64, - change_address: &OrchardAddress, - fvk: &FullViewingKey, - ask: &SpendAuthorizingKey, - anchor: Anchor, - proving_key: &ProvingKey, - memo: [u8; 36], - fee: Option, - platform_version: &PlatformVersion, -) -> Result { - let total_spent: u64 = spends.iter().map(|s| s.note.value().inner()).sum(); - - // Conservative action count: at least (spends, 2) since we always have - // a recipient output and likely a change output. - let num_actions = spends.len().max(2); - let min_fee = compute_minimum_shielded_fee(num_actions, platform_version); - let effective_fee = match fee { - Some(f) if f < min_fee => { - return Err(ProtocolError::Generic(format!( - "fee {} is below minimum required fee {}", - f, min_fee - ))); - } - Some(f) => f, - None => min_fee, - }; - - let required = transfer_amount - .checked_add(effective_fee) - .ok_or_else(|| ProtocolError::Generic("fee + transfer_amount overflows u64".to_string()))?; - if required > total_spent { - return Err(ProtocolError::Generic(format!( - "transfer amount {} + fee {} = {} exceeds total spendable value {}", - transfer_amount, effective_fee, required, total_spent - ))); - } - - let change_amount = total_spent - required; - - let recipient_payment = PaymentAddress::from(recipient); - - let mut builder = Builder::::new(BundleType::DEFAULT, anchor); - - for spend in spends { - builder - .add_spend(fvk.clone(), spend.note, spend.merkle_path) - .map_err(|e| ProtocolError::Generic(format!("failed to add spend: {:?}", e)))?; - } - - // Primary output to recipient - builder - .add_output( - None, - recipient_payment, - NoteValue::from_raw(transfer_amount), - memo, - ) - .map_err(|e| ProtocolError::Generic(format!("failed to add output: {:?}", e)))?; - - // Change output (if any) - if change_amount > 0 { - let change_payment = PaymentAddress::from(change_address); - builder - .add_output( - None, - change_payment, - NoteValue::from_raw(change_amount), - [0u8; 36], - ) - .map_err(|e| ProtocolError::Generic(format!("failed to add change output: {:?}", e)))?; - } - - // ShieldedTransfer has no extra_data in sighash - let bundle = prove_and_sign_bundle(builder, proving_key, std::slice::from_ref(ask), &[])?; - let sb = serialize_authorized_bundle(&bundle); - - // value_balance = effective_fee (the amount leaving the shielded pool as fee) - ShieldedTransferTransition::try_from_bundle( - sb.actions, - sb.value_balance as u64, - sb.anchor, - sb.proof, - sb.binding_signature, - platform_version, - ) -} - -/// Builds an Unshield state transition (shielded pool -> platform address). -/// -/// Spends existing notes and sends part of the value to a transparent platform -/// address. The shielded fee is deducted from the spent notes. Any remaining -/// value is returned to the shielded `change_address`. -/// -/// # Parameters -/// - `spends` - Notes to spend with their Merkle paths -/// - `output_address` - Platform address to receive the unshielded funds -/// - `unshield_amount` - Amount to unshield to the platform address -/// - `change_address` - Orchard address for change output -/// - `fvk` - Full viewing key for spend authorization -/// - `ask` - Spend authorizing key for RedPallas signatures -/// - `anchor` - Sinsemilla root of the note commitment tree (Orchard Anchor) -/// - `proving_key` - Halo 2 proving key -/// - `memo` - 36-byte structured memo for the change output (4-byte type tag + 32-byte payload) -/// - `fee` - Optional fee override; if `None`, the minimum fee is computed automatically. -/// If `Some`, must be >= the minimum fee. -/// - `platform_version` - Protocol version -#[allow(clippy::too_many_arguments)] -pub fn build_unshield_transition( - spends: Vec, - output_address: PlatformAddress, - unshield_amount: u64, - change_address: &OrchardAddress, - fvk: &FullViewingKey, - ask: &SpendAuthorizingKey, - anchor: Anchor, - proving_key: &ProvingKey, - memo: [u8; 36], - fee: Option, - platform_version: &PlatformVersion, -) -> Result { - let total_spent: u64 = spends.iter().map(|s| s.note.value().inner()).sum(); - - // Conservative action count: at least (spends, 1) since we have a change output. - let num_actions = spends.len().max(1); - let min_fee = compute_minimum_shielded_fee(num_actions, platform_version); - let effective_fee = match fee { - Some(f) if f < min_fee => { - return Err(ProtocolError::Generic(format!( - "fee {} is below minimum required fee {}", - f, min_fee - ))); - } - Some(f) => f, - None => min_fee, - }; - - let required = unshield_amount - .checked_add(effective_fee) - .ok_or_else(|| ProtocolError::Generic("fee + unshield_amount overflows u64".to_string()))?; - if required > total_spent { - return Err(ProtocolError::Generic(format!( - "unshield amount {} + fee {} = {} exceeds total spendable value {}", - unshield_amount, effective_fee, required, total_spent - ))); - } - - let change_amount = total_spent - required; - - // Unshield extra_data = output_address.to_bytes() || amount.to_le_bytes() - let mut extra_sighash_data = output_address.to_bytes(); - extra_sighash_data.extend_from_slice(&unshield_amount.to_le_bytes()); - - let bundle = build_spend_bundle( - spends, - change_address, - change_amount, - memo, - fvk, - ask, - anchor, - proving_key, - &extra_sighash_data, - )?; - - let sb = serialize_authorized_bundle(&bundle); - - UnshieldTransition::try_from_bundle( - output_address, - unshield_amount, - sb.actions, - sb.value_balance, - sb.anchor, - sb.proof, - sb.binding_signature, - platform_version, - ) -} - -/// Builds a ShieldedWithdrawal state transition (shielded pool -> core L1 address). -/// -/// Spends existing notes and withdraws value to a core chain script output. -/// The shielded fee is deducted from the spent notes. Any remaining value is -/// returned to the shielded `change_address`. -/// -/// # Parameters -/// - `spends` - Notes to spend with their Merkle paths -/// - `withdrawal_amount` - Amount to withdraw to the core chain -/// - `output_script` - Core chain script to receive the funds -/// - `core_fee_per_byte` - Core chain fee rate -/// - `pooling` - Withdrawal pooling strategy -/// - `change_address` - Orchard address for change output -/// - `fvk` - Full viewing key for spend authorization -/// - `ask` - Spend authorizing key for RedPallas signatures -/// - `anchor` - Sinsemilla root of the note commitment tree (Orchard Anchor) -/// - `proving_key` - Halo 2 proving key -/// - `memo` - 36-byte structured memo for the change output (4-byte type tag + 32-byte payload) -/// - `fee` - Optional fee override; if `None`, the minimum fee is computed automatically. -/// If `Some`, must be >= the minimum fee. -/// - `platform_version` - Protocol version -#[allow(clippy::too_many_arguments)] -pub fn build_shielded_withdrawal_transition( - spends: Vec, - withdrawal_amount: u64, - output_script: CoreScript, - core_fee_per_byte: u32, - pooling: Pooling, - change_address: &OrchardAddress, - fvk: &FullViewingKey, - ask: &SpendAuthorizingKey, - anchor: Anchor, - proving_key: &ProvingKey, - memo: [u8; 36], - fee: Option, - platform_version: &PlatformVersion, -) -> Result { - let total_spent: u64 = spends.iter().map(|s| s.note.value().inner()).sum(); - - // Conservative action count: at least (spends, 1) since we have a change output. - let num_actions = spends.len().max(1); - let min_fee = compute_minimum_shielded_fee(num_actions, platform_version); - let effective_fee = match fee { - Some(f) if f < min_fee => { - return Err(ProtocolError::Generic(format!( - "fee {} is below minimum required fee {}", - f, min_fee - ))); - } - Some(f) => f, - None => min_fee, - }; - - let required = withdrawal_amount - .checked_add(effective_fee) - .ok_or_else(|| { - ProtocolError::Generic("fee + withdrawal_amount overflows u64".to_string()) - })?; - if required > total_spent { - return Err(ProtocolError::Generic(format!( - "withdrawal amount {} + fee {} = {} exceeds total spendable value {}", - withdrawal_amount, effective_fee, required, total_spent - ))); - } - - let change_amount = total_spent - required; - - // ShieldedWithdrawal extra_data = output_script.as_bytes() || amount.to_le_bytes() - let mut extra_sighash_data = output_script.as_bytes().to_vec(); - extra_sighash_data.extend_from_slice(&withdrawal_amount.to_le_bytes()); - - let bundle = build_spend_bundle( - spends, - change_address, - change_amount, - memo, - fvk, - ask, - anchor, - proving_key, - &extra_sighash_data, - )?; - - let sb = serialize_authorized_bundle(&bundle); - - ShieldedWithdrawalTransition::try_from_bundle( - withdrawal_amount, - sb.actions, - sb.value_balance, - sb.anchor, - sb.proof, - sb.binding_signature, - core_fee_per_byte, - pooling, - output_script, - platform_version, - ) -} diff --git a/packages/rs-dpp/src/shielded/builder/mod.rs b/packages/rs-dpp/src/shielded/builder/mod.rs new file mode 100644 index 00000000000..84558c4b5e6 --- /dev/null +++ b/packages/rs-dpp/src/shielded/builder/mod.rs @@ -0,0 +1,277 @@ +//! Convenience builders for constructing shielded state transitions. +//! +//! These functions encapsulate the full Orchard bundle construction pipeline: +//! builder configuration, proof generation, signature application, +//! and serialization into platform state transitions. +//! +//! Requires the `shielded-tx` feature, which pulls in +//! `grovedb-commitment-tree` (and transitively the `orchard` crate). +//! +//! # Example +//! +//! ```ignore +//! use dpp::shielded::builder::*; +//! use grovedb_commitment_tree::{SpendingKey, FullViewingKey, Scope, ProvingKey}; +//! +//! // Derive recipient address +//! let sk = SpendingKey::from_bytes(seed)?; +//! let fvk = FullViewingKey::from(&sk); +//! let recipient = OrchardAddress::from_raw_bytes( +//! &fvk.address_at(0, Scope::External).to_raw_address_bytes(), +//! ); +//! +//! // Build a shield transition +//! let pk = ProvingKey::build(); +//! let st = build_shield_transition( +//! &recipient, shield_amount, inputs, fee_strategy, +//! &signer, 0, &pk, [0u8; 36], platform_version, +//! )?; +//! ``` + +mod shield; +mod shield_from_asset_lock; +mod shielded_transfer; +mod shielded_withdrawal; +mod unshield; + +pub use shield::build_shield_transition; +pub use shield_from_asset_lock::build_shield_from_asset_lock_transition; +pub use shielded_transfer::build_shielded_transfer_transition; +pub use shielded_withdrawal::build_shielded_withdrawal_transition; +pub use unshield::build_unshield_transition; + +use grovedb_commitment_tree::{ + Anchor, Authorized, Builder, Bundle, BundleType, DashMemo, Flags as OrchardFlags, + FullViewingKey, MerklePath, Note, NoteValue, PaymentAddress, ProvingKey, SpendAuthorizingKey, +}; +use rand::rngs::OsRng; + +use crate::address_funds::OrchardAddress; +use crate::shielded::{compute_platform_sighash, SerializedAction}; +use crate::ProtocolError; + +/// A note that can be spent in a shielded transaction, paired with its +/// Merkle inclusion path in the commitment tree. +pub struct SpendableNote { + /// The Orchard note to spend. + pub note: Note, + /// Merkle path proving the note's commitment exists in the tree. + pub merkle_path: MerklePath, +} + +/// The serialized fields extracted from an authorized Orchard bundle, +/// ready for use by state transition constructors. +pub struct SerializedBundle { + /// Serialized Orchard actions (spends + outputs). + pub actions: Vec, + /// Bundle flags byte. + pub flags: u8, + /// Net value balance (positive = value leaving the shielded pool). + pub value_balance: i64, + /// Sinsemilla root of the Orchard note commitment tree (32 bytes). + /// This is the Orchard `Anchor` — the root hash of the depth-32 Sinsemilla + /// Merkle tree over extracted note commitments (cmx values). + pub anchor: [u8; 32], + /// Halo 2 proof bytes. + pub proof: Vec, + /// Binding signature (64 bytes). + pub binding_signature: [u8; 64], +} + +impl From<&OrchardAddress> for PaymentAddress { + fn from(address: &OrchardAddress) -> Self { + *address.inner() + } +} + +/// Serializes an authorized Orchard bundle into the raw fields used by +/// state transition constructors. +pub fn serialize_authorized_bundle(bundle: &Bundle) -> SerializedBundle { + let actions: Vec = bundle + .actions() + .iter() + .map(|action| { + let enc = action.encrypted_note(); + let mut encrypted_note = Vec::with_capacity(216); + encrypted_note.extend_from_slice(&enc.epk_bytes); + encrypted_note.extend_from_slice(enc.enc_ciphertext.as_ref()); + encrypted_note.extend_from_slice(&enc.out_ciphertext); + SerializedAction { + nullifier: action.nullifier().to_bytes(), + rk: <[u8; 32]>::from(action.rk()), + cmx: action.cmx().to_bytes(), + encrypted_note, + cv_net: action.cv_net().to_bytes(), + spend_auth_sig: <[u8; 64]>::from(action.authorization()), + } + }) + .collect(); + let flags = bundle.flags().to_byte(); + let value_balance = *bundle.value_balance(); + let anchor = bundle.anchor().to_bytes(); + let proof = bundle.authorization().proof().as_ref().to_vec(); + let binding_signature = <[u8; 64]>::from(bundle.authorization().binding_signature()); + SerializedBundle { + actions, + flags, + value_balance, + anchor, + proof, + binding_signature, + } +} + +// --------------------------------------------------------------------------- +// Internal helpers +// --------------------------------------------------------------------------- + +/// Builds an output-only Orchard bundle (no spends). +/// +/// Used by Shield and ShieldFromAssetLock transitions where funds enter +/// the shielded pool from transparent sources. +pub(crate) fn build_output_only_bundle( + recipient: &OrchardAddress, + amount: u64, + memo: [u8; 36], + proving_key: &ProvingKey, +) -> Result, ProtocolError> { + let payment_address = PaymentAddress::from(recipient); + let anchor = Anchor::empty_tree(); + let mut builder = Builder::::new( + BundleType::Transactional { + flags: OrchardFlags::SPENDS_DISABLED, + bundle_required: false, + }, + anchor, + ); + + builder + .add_output(None, payment_address, NoteValue::from_raw(amount), memo) + .map_err(|e| ProtocolError::Generic(format!("failed to add output: {:?}", e)))?; + + prove_and_sign_bundle(builder, proving_key, &[], &[]) +} + +/// Builds a spend+output Orchard bundle. +/// +/// Used by ShieldedTransfer, Unshield, and ShieldedWithdrawal where funds +/// are spent from existing notes. +#[allow(clippy::too_many_arguments)] +pub(crate) fn build_spend_bundle( + spends: Vec, + recipient: &OrchardAddress, + output_amount: u64, + memo: [u8; 36], + fvk: &FullViewingKey, + ask: &SpendAuthorizingKey, + anchor: Anchor, + proving_key: &ProvingKey, + extra_sighash_data: &[u8], +) -> Result, ProtocolError> { + let payment_address = PaymentAddress::from(recipient); + + let mut builder = Builder::::new(BundleType::DEFAULT, anchor); + + for spend in spends { + builder + .add_spend(fvk.clone(), spend.note, spend.merkle_path) + .map_err(|e| ProtocolError::Generic(format!("failed to add spend: {:?}", e)))?; + } + + builder + .add_output( + None, + payment_address, + NoteValue::from_raw(output_amount), + memo, + ) + .map_err(|e| ProtocolError::Generic(format!("failed to add output: {:?}", e)))?; + + prove_and_sign_bundle( + builder, + proving_key, + std::slice::from_ref(ask), + extra_sighash_data, + ) +} + +/// Takes a configured Builder, generates the proof, computes the platform +/// sighash, and applies signatures. +pub(crate) fn prove_and_sign_bundle( + builder: Builder, + proving_key: &ProvingKey, + signing_keys: &[SpendAuthorizingKey], + extra_sighash_data: &[u8], +) -> Result, ProtocolError> { + let mut rng = OsRng; + + let (unauthorized, _) = builder + .build::(&mut rng) + .map_err(|e| ProtocolError::Generic(format!("failed to build bundle: {:?}", e)))? + .ok_or_else(|| ProtocolError::Generic("bundle was empty after build".to_string()))?; + + let bundle_commitment: [u8; 32] = unauthorized.commitment().into(); + let sighash = compute_platform_sighash(&bundle_commitment, extra_sighash_data); + + let proven = unauthorized + .create_proof(proving_key, &mut rng) + .map_err(|e| ProtocolError::Generic(format!("failed to create proof: {:?}", e)))?; + + proven + .apply_signatures(rng, sighash, signing_keys) + .map_err(|e| ProtocolError::Generic(format!("failed to apply signatures: {:?}", e))) +} + +/// Shared test utilities for builder tests. +#[cfg(test)] +pub(crate) mod test_helpers { + use super::*; + use grovedb_commitment_tree::{ + FullViewingKey, Hashable, MerkleHashOrchard, Note, NoteValue, ProvingKey, RandomSeed, Rho, + Scope, SpendingKey, NOTE_COMMITMENT_TREE_DEPTH, + }; + use std::sync::OnceLock; + + static PROVING_KEY: OnceLock = OnceLock::new(); + + /// Returns a cached ProvingKey (~30s to build on first call). + pub fn proving_key() -> &'static ProvingKey { + PROVING_KEY.get_or_init(ProvingKey::build) + } + + /// Creates a test OrchardAddress from a deterministic spending key. + pub fn test_orchard_address() -> OrchardAddress { + let sk = SpendingKey::from_bytes([42u8; 32]).expect("valid spending key bytes"); + let fvk = FullViewingKey::from(&sk); + let payment_address = fvk.address_at(0u32, Scope::External); + OrchardAddress::from_raw_bytes(&payment_address.to_raw_address_bytes()) + .expect("valid orchard address bytes") + } + + /// Creates a SpendableNote with the given value. + /// + /// The note is cryptographically valid (has a valid commitment) but uses + /// an all-zeros Merkle path, so it will only pass the Orchard circuit when + /// paired with `Anchor::empty_tree()`. Suitable for both error-path tests + /// (where the proving key is never reached) and happy-path tests. + pub fn test_spendable_note(value: u64) -> SpendableNote { + let sk = SpendingKey::from_bytes([42u8; 32]).expect("valid spending key bytes"); + let fvk = FullViewingKey::from(&sk); + let payment_address = fvk.address_at(0u32, Scope::External); + + // Construct a valid Rho from the zero element (always valid in pallas) + let rho: Rho = + Option::from(Rho::from_bytes(&[0u8; 32])).expect("zero is valid pallas::Base"); + let rseed: RandomSeed = + Option::from(RandomSeed::from_bytes([1u8; 32], &rho)).expect("valid random seed"); + let note: Note = + Option::from(Note::from_parts(payment_address, NoteValue::from_raw(value), rho, rseed)) + .expect("note commitment should be valid"); + + // All-zeros merkle path at position 0 — consistent with Anchor::empty_tree() + let auth_path = [MerkleHashOrchard::empty_leaf(); NOTE_COMMITMENT_TREE_DEPTH]; + let merkle_path = MerklePath::from_parts(0, auth_path); + + SpendableNote { note, merkle_path } + } +} diff --git a/packages/rs-dpp/src/shielded/builder/shield.rs b/packages/rs-dpp/src/shielded/builder/shield.rs new file mode 100644 index 00000000000..bc5f0d288ed --- /dev/null +++ b/packages/rs-dpp/src/shielded/builder/shield.rs @@ -0,0 +1,159 @@ +use std::collections::BTreeMap; + +use grovedb_commitment_tree::ProvingKey; + +use crate::address_funds::AddressFundsFeeStrategy; +use crate::address_funds::{OrchardAddress, PlatformAddress}; +use crate::fee::Credits; +use crate::identity::signer::Signer; +use crate::prelude::{AddressNonce, UserFeeIncrease}; +use crate::state_transition::shield_transition::methods::ShieldTransitionMethodsV0; +use crate::state_transition::shield_transition::ShieldTransition; +use crate::state_transition::StateTransition; +use crate::ProtocolError; +use platform_version::version::PlatformVersion; + +use super::{build_output_only_bundle, serialize_authorized_bundle}; + +/// Builds a Shield state transition (transparent platform addresses -> shielded pool). +/// +/// Constructs an output-only Orchard bundle (no spends), proves it, signs the +/// transparent input witnesses, and returns a ready-to-broadcast `StateTransition`. +/// +/// # Parameters +/// - `recipient` - Orchard address to receive the shielded note +/// - `shield_amount` - Amount of credits to shield +/// - `inputs` - Platform address inputs with their nonces and balances +/// - `fee_strategy` - How to deduct fees from the transparent inputs +/// - `signer` - Signs each input address witness (ECDSA) +/// - `user_fee_increase` - Fee multiplier (0 = 100% base fee) +/// - `proving_key` - Halo 2 proving key (cache with `OnceLock` — ~30s to build) +/// - `memo` - 36-byte structured memo for the recipient (4-byte type tag + 32-byte payload) +/// - `platform_version` - Protocol version +#[allow(clippy::too_many_arguments)] +pub fn build_shield_transition>( + recipient: &OrchardAddress, + shield_amount: u64, + inputs: BTreeMap, + fee_strategy: AddressFundsFeeStrategy, + signer: &S, + user_fee_increase: UserFeeIncrease, + proving_key: &ProvingKey, + memo: [u8; 36], + platform_version: &PlatformVersion, +) -> Result { + if fee_strategy.is_empty() { + return Err(ProtocolError::Generic( + "fee_strategy must have at least one step".to_string(), + )); + } + + let bundle = build_output_only_bundle(recipient, shield_amount, memo, proving_key)?; + let sb = serialize_authorized_bundle(&bundle); + + ShieldTransition::try_from_bundle_with_signer( + inputs, + sb.actions, + sb.value_balance, + sb.anchor, + sb.proof, + sb.binding_signature, + fee_strategy, + signer, + user_fee_increase, + platform_version, + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::address_funds::AddressWitness; + use crate::address_funds::AddressFundsFeeStrategyStep; + use crate::shielded::builder::test_helpers::{proving_key, test_orchard_address}; + use platform_value::BinaryData; + + /// A dummy signer that produces a fake 65-byte signature. + /// Only used to test the builder pipeline — the signature is not validated here. + #[derive(Debug)] + struct DummySigner; + + impl Signer for DummySigner { + fn sign(&self, _key: &PlatformAddress, _data: &[u8]) -> Result { + Ok(BinaryData::new(vec![0u8; 65])) + } + + fn sign_create_witness( + &self, + _key: &PlatformAddress, + _data: &[u8], + ) -> Result { + Ok(AddressWitness::P2pkh { + signature: BinaryData::new(vec![0u8; 65]), + }) + } + + fn can_sign_with(&self, _key: &PlatformAddress) -> bool { + true + } + } + + #[test] + fn test_build_shield_empty_fee_strategy() { + let recipient = test_orchard_address(); + let platform_version = PlatformVersion::latest(); + let pk = proving_key(); + + let result = build_shield_transition( + &recipient, + 1000, + BTreeMap::new(), + vec![], // empty fee strategy + &DummySigner, + 0, + pk, + [0u8; 36], + platform_version, + ); + + assert!(result.is_err()); + let err = result.unwrap_err().to_string(); + assert!( + err.contains("fee_strategy must have at least one step"), + "unexpected error: {}", + err + ); + } + + #[test] + fn test_build_shield_transition_valid() { + let recipient = test_orchard_address(); + let platform_version = PlatformVersion::latest(); + let pk = proving_key(); + + // Create a P2PKH address as input + let input_address = PlatformAddress::P2pkh([1u8; 20]); + let mut inputs = BTreeMap::new(); + inputs.insert(input_address, (0u32, 100_000u64)); + + let fee_strategy = vec![AddressFundsFeeStrategyStep::DeductFromInput(0)]; + + let result = build_shield_transition( + &recipient, + 50_000, + inputs, + fee_strategy, + &DummySigner, + 0, + pk, + [0u8; 36], + platform_version, + ); + + assert!(result.is_ok(), "expected Ok, got: {:?}", result.err()); + match result.unwrap() { + StateTransition::Shield(_) => {} // correct variant + other => panic!("expected Shield variant, got {:?}", other), + } + } +} diff --git a/packages/rs-dpp/src/shielded/builder/shield_from_asset_lock.rs b/packages/rs-dpp/src/shielded/builder/shield_from_asset_lock.rs new file mode 100644 index 00000000000..0758c052456 --- /dev/null +++ b/packages/rs-dpp/src/shielded/builder/shield_from_asset_lock.rs @@ -0,0 +1,99 @@ +use grovedb_commitment_tree::ProvingKey; + +use crate::address_funds::OrchardAddress; +use crate::prelude::{AssetLockProof, UserFeeIncrease}; +use crate::state_transition::shield_from_asset_lock_transition::methods::ShieldFromAssetLockTransitionMethodsV0; +use crate::state_transition::shield_from_asset_lock_transition::ShieldFromAssetLockTransition; +use crate::state_transition::StateTransition; +use crate::ProtocolError; +use platform_version::version::PlatformVersion; + +use super::{build_output_only_bundle, serialize_authorized_bundle}; + +/// Builds a ShieldFromAssetLock state transition (core asset lock -> shielded pool). +/// +/// Like Shield, constructs an output-only Orchard bundle. The funds come from +/// a core asset lock proof rather than platform address inputs. +/// +/// # Parameters +/// - `recipient` - Orchard address to receive the shielded note +/// - `shield_amount` - Amount of credits to shield (from the asset lock) +/// - `asset_lock_proof` - Proof that funds are locked on core chain +/// - `asset_lock_private_key` - Private key for the asset lock (signs the transition) +/// - `user_fee_increase` - Fee multiplier (0 = 100% base fee) +/// - `proving_key` - Halo 2 proving key +/// - `memo` - 36-byte structured memo for the recipient (4-byte type tag + 32-byte payload) +/// - `platform_version` - Protocol version +#[allow(clippy::too_many_arguments)] +pub fn build_shield_from_asset_lock_transition( + recipient: &OrchardAddress, + shield_amount: u64, + asset_lock_proof: AssetLockProof, + asset_lock_private_key: &[u8], + user_fee_increase: UserFeeIncrease, + proving_key: &ProvingKey, + memo: [u8; 36], + platform_version: &PlatformVersion, +) -> Result { + let bundle = build_output_only_bundle(recipient, shield_amount, memo, proving_key)?; + let sb = serialize_authorized_bundle(&bundle); + + // For output-only bundles, Orchard value_balance is negative (value flowing in). + // Convert to u64 (absolute amount entering the pool). + let value_balance = sb + .value_balance + .checked_neg() + .and_then(|v| u64::try_from(v).ok()) + .ok_or_else(|| { + ProtocolError::Generic( + "shield_from_asset_lock: bundle value_balance is not negative".to_string(), + ) + })?; + + ShieldFromAssetLockTransition::try_from_asset_lock_with_bundle( + asset_lock_proof, + asset_lock_private_key, + sb.actions, + value_balance, + sb.anchor, + sb.proof, + sb.binding_signature, + user_fee_increase, + platform_version, + ) +} + +#[cfg(test)] +mod tests { + use super::super::{build_output_only_bundle, serialize_authorized_bundle}; + use crate::shielded::builder::test_helpers::{proving_key, test_orchard_address}; + + /// Verifies that an output-only bundle produces a negative value_balance + /// (value flowing into the pool), which is the precondition for + /// shield_from_asset_lock's value_balance conversion. + #[test] + fn test_output_only_bundle_value_balance_is_negative() { + let recipient = test_orchard_address(); + let pk = proving_key(); + let amount = 50_000u64; + + let bundle = build_output_only_bundle(&recipient, amount, [0u8; 36], pk) + .expect("bundle should build successfully"); + let sb = serialize_authorized_bundle(&bundle); + + // Output-only bundles have negative value_balance (value entering the pool) + assert!( + sb.value_balance < 0, + "expected negative value_balance, got {}", + sb.value_balance + ); + + // The absolute value should match the shield amount + let abs_balance = sb + .value_balance + .checked_neg() + .and_then(|v| u64::try_from(v).ok()) + .expect("value_balance should be safely negatable"); + assert_eq!(abs_balance, amount); + } +} diff --git a/packages/rs-dpp/src/shielded/builder/shielded_transfer.rs b/packages/rs-dpp/src/shielded/builder/shielded_transfer.rs new file mode 100644 index 00000000000..7efa94db9ad --- /dev/null +++ b/packages/rs-dpp/src/shielded/builder/shielded_transfer.rs @@ -0,0 +1,208 @@ +use grovedb_commitment_tree::{ + Anchor, Builder, BundleType, DashMemo, FullViewingKey, NoteValue, PaymentAddress, ProvingKey, + SpendAuthorizingKey, +}; + +use crate::address_funds::OrchardAddress; +use crate::fee::Credits; +use crate::shielded::compute_minimum_shielded_fee; +use crate::state_transition::shielded_transfer_transition::methods::ShieldedTransferTransitionMethodsV0; +use crate::state_transition::shielded_transfer_transition::ShieldedTransferTransition; +use crate::state_transition::StateTransition; +use crate::ProtocolError; +use platform_version::version::PlatformVersion; + +use super::{prove_and_sign_bundle, serialize_authorized_bundle, SpendableNote}; + +/// Builds a ShieldedTransfer state transition (shielded pool -> shielded pool). +/// +/// Spends existing notes and creates a new note for the recipient. The shielded +/// fee is deducted from the spent notes. Any remaining change is returned to +/// the `change_address`. +/// +/// # Parameters +/// - `spends` - Notes to spend with their Merkle paths +/// - `recipient` - Orchard address to receive the transferred note +/// - `transfer_amount` - Amount to transfer to the recipient +/// - `change_address` - Orchard address for change output (if any) +/// - `fvk` - Full viewing key for spend authorization +/// - `ask` - Spend authorizing key for RedPallas signatures +/// - `anchor` - Sinsemilla root of the note commitment tree (Orchard Anchor) +/// - `proving_key` - Halo 2 proving key +/// - `memo` - 36-byte structured memo for the recipient (4-byte type tag + 32-byte payload) +/// - `fee` - Optional fee override; if `None`, the minimum fee is computed automatically. +/// If `Some`, must be >= the minimum fee. +/// - `platform_version` - Protocol version +#[allow(clippy::too_many_arguments)] +pub fn build_shielded_transfer_transition( + spends: Vec, + recipient: &OrchardAddress, + transfer_amount: u64, + change_address: &OrchardAddress, + fvk: &FullViewingKey, + ask: &SpendAuthorizingKey, + anchor: Anchor, + proving_key: &ProvingKey, + memo: [u8; 36], + fee: Option, + platform_version: &PlatformVersion, +) -> Result { + let total_spent: u64 = spends.iter().map(|s| s.note.value().inner()).sum(); + + // Conservative action count: at least (spends, 2) since we always have + // a recipient output and likely a change output. + let num_actions = spends.len().max(2); + let min_fee = compute_minimum_shielded_fee(num_actions, platform_version); + let effective_fee = match fee { + Some(f) if f < min_fee => { + return Err(ProtocolError::Generic(format!( + "fee {} is below minimum required fee {}", + f, min_fee + ))); + } + Some(f) => f, + None => min_fee, + }; + + let required = transfer_amount + .checked_add(effective_fee) + .ok_or_else(|| ProtocolError::Generic("fee + transfer_amount overflows u64".to_string()))?; + if required > total_spent { + return Err(ProtocolError::Generic(format!( + "transfer amount {} + fee {} = {} exceeds total spendable value {}", + transfer_amount, effective_fee, required, total_spent + ))); + } + + let change_amount = total_spent - required; + + let recipient_payment = PaymentAddress::from(recipient); + + let mut builder = Builder::::new(BundleType::DEFAULT, anchor); + + for spend in spends { + builder + .add_spend(fvk.clone(), spend.note, spend.merkle_path) + .map_err(|e| ProtocolError::Generic(format!("failed to add spend: {:?}", e)))?; + } + + // Primary output to recipient + builder + .add_output( + None, + recipient_payment, + NoteValue::from_raw(transfer_amount), + memo, + ) + .map_err(|e| ProtocolError::Generic(format!("failed to add output: {:?}", e)))?; + + // Change output (if any) + if change_amount > 0 { + let change_payment = PaymentAddress::from(change_address); + builder + .add_output( + None, + change_payment, + NoteValue::from_raw(change_amount), + [0u8; 36], + ) + .map_err(|e| ProtocolError::Generic(format!("failed to add change output: {:?}", e)))?; + } + + // ShieldedTransfer has no extra_data in sighash + let bundle = prove_and_sign_bundle(builder, proving_key, std::slice::from_ref(ask), &[])?; + let sb = serialize_authorized_bundle(&bundle); + + // value_balance = effective_fee (the amount leaving the shielded pool as fee) + ShieldedTransferTransition::try_from_bundle( + sb.actions, + sb.value_balance as u64, + sb.anchor, + sb.proof, + sb.binding_signature, + platform_version, + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::shielded::builder::test_helpers::{ + proving_key, test_orchard_address, test_spendable_note, + }; + + #[test] + fn test_shielded_transfer_fee_below_minimum() { + let platform_version = PlatformVersion::latest(); + let recipient = test_orchard_address(); + let change_address = test_orchard_address(); + + let note = test_spendable_note(1_000_000); + let spends = vec![note]; + + let sk = grovedb_commitment_tree::SpendingKey::from_bytes([42u8; 32]) + .expect("valid spending key bytes"); + let fvk = FullViewingKey::from(&sk); + let ask = SpendAuthorizingKey::from(&sk); + + let result = build_shielded_transfer_transition( + spends, + &recipient, + 100, + &change_address, + &fvk, + &ask, + Anchor::empty_tree(), + proving_key(), + [0u8; 36], + Some(1), // fee = 1, should be below minimum + platform_version, + ); + + assert!(result.is_err()); + let err = result.unwrap_err().to_string(); + assert!( + err.contains("below minimum required fee"), + "unexpected error: {}", + err + ); + } + + #[test] + fn test_shielded_transfer_insufficient_funds() { + let platform_version = PlatformVersion::latest(); + let recipient = test_orchard_address(); + let change_address = test_orchard_address(); + + // Note with only 100 credits + let note = test_spendable_note(100); + let spends = vec![note]; + + let sk = grovedb_commitment_tree::SpendingKey::from_bytes([42u8; 32]) + .expect("valid spending key bytes"); + let fvk = FullViewingKey::from(&sk); + let ask = SpendAuthorizingKey::from(&sk); + + let result = build_shielded_transfer_transition( + spends, + &recipient, + 1_000_000, + &change_address, + &fvk, + &ask, + Anchor::empty_tree(), + proving_key(), + [0u8; 36], + None, + platform_version, + ); + + assert!(result.is_err()); + let err = result.unwrap_err().to_string(); + assert!( + err.contains("exceeds total spendable value"), + "unexpected error: {}", + err + ); + } +} diff --git a/packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs b/packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs new file mode 100644 index 00000000000..0f4a795d561 --- /dev/null +++ b/packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs @@ -0,0 +1,197 @@ +use grovedb_commitment_tree::{Anchor, FullViewingKey, ProvingKey, SpendAuthorizingKey}; + +use crate::address_funds::OrchardAddress; +use crate::fee::Credits; +use crate::identity::core_script::CoreScript; +use crate::shielded::compute_minimum_shielded_fee; +use crate::state_transition::shielded_withdrawal_transition::methods::ShieldedWithdrawalTransitionMethodsV0; +use crate::state_transition::shielded_withdrawal_transition::ShieldedWithdrawalTransition; +use crate::state_transition::StateTransition; +use crate::withdrawal::Pooling; +use crate::ProtocolError; +use platform_version::version::PlatformVersion; + +use super::{build_spend_bundle, serialize_authorized_bundle, SpendableNote}; + +/// Builds a ShieldedWithdrawal state transition (shielded pool -> core L1 address). +/// +/// Spends existing notes and withdraws value to a core chain script output. +/// The shielded fee is deducted from the spent notes. Any remaining value is +/// returned to the shielded `change_address`. +/// +/// # Parameters +/// - `spends` - Notes to spend with their Merkle paths +/// - `withdrawal_amount` - Amount to withdraw to the core chain +/// - `output_script` - Core chain script to receive the funds +/// - `core_fee_per_byte` - Core chain fee rate +/// - `pooling` - Withdrawal pooling strategy +/// - `change_address` - Orchard address for change output +/// - `fvk` - Full viewing key for spend authorization +/// - `ask` - Spend authorizing key for RedPallas signatures +/// - `anchor` - Sinsemilla root of the note commitment tree (Orchard Anchor) +/// - `proving_key` - Halo 2 proving key +/// - `memo` - 36-byte structured memo for the change output (4-byte type tag + 32-byte payload) +/// - `fee` - Optional fee override; if `None`, the minimum fee is computed automatically. +/// If `Some`, must be >= the minimum fee. +/// - `platform_version` - Protocol version +#[allow(clippy::too_many_arguments)] +pub fn build_shielded_withdrawal_transition( + spends: Vec, + withdrawal_amount: u64, + output_script: CoreScript, + core_fee_per_byte: u32, + pooling: Pooling, + change_address: &OrchardAddress, + fvk: &FullViewingKey, + ask: &SpendAuthorizingKey, + anchor: Anchor, + proving_key: &ProvingKey, + memo: [u8; 36], + fee: Option, + platform_version: &PlatformVersion, +) -> Result { + let total_spent: u64 = spends.iter().map(|s| s.note.value().inner()).sum(); + + // Conservative action count: at least (spends, 1) since we have a change output. + let num_actions = spends.len().max(1); + let min_fee = compute_minimum_shielded_fee(num_actions, platform_version); + let effective_fee = match fee { + Some(f) if f < min_fee => { + return Err(ProtocolError::Generic(format!( + "fee {} is below minimum required fee {}", + f, min_fee + ))); + } + Some(f) => f, + None => min_fee, + }; + + let required = withdrawal_amount + .checked_add(effective_fee) + .ok_or_else(|| { + ProtocolError::Generic("fee + withdrawal_amount overflows u64".to_string()) + })?; + if required > total_spent { + return Err(ProtocolError::Generic(format!( + "withdrawal amount {} + fee {} = {} exceeds total spendable value {}", + withdrawal_amount, effective_fee, required, total_spent + ))); + } + + let change_amount = total_spent - required; + + // ShieldedWithdrawal extra_data = output_script.as_bytes() || amount.to_le_bytes() + let mut extra_sighash_data = output_script.as_bytes().to_vec(); + extra_sighash_data.extend_from_slice(&withdrawal_amount.to_le_bytes()); + + let bundle = build_spend_bundle( + spends, + change_address, + change_amount, + memo, + fvk, + ask, + anchor, + proving_key, + &extra_sighash_data, + )?; + + let sb = serialize_authorized_bundle(&bundle); + + ShieldedWithdrawalTransition::try_from_bundle( + withdrawal_amount, + sb.actions, + sb.value_balance, + sb.anchor, + sb.proof, + sb.binding_signature, + core_fee_per_byte, + pooling, + output_script, + platform_version, + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::shielded::builder::test_helpers::{ + proving_key, test_orchard_address, test_spendable_note, + }; + + #[test] + fn test_shielded_withdrawal_fee_below_minimum() { + let platform_version = PlatformVersion::latest(); + let change_address = test_orchard_address(); + + let note = test_spendable_note(1_000_000); + let spends = vec![note]; + + let sk = grovedb_commitment_tree::SpendingKey::from_bytes([42u8; 32]) + .expect("valid spending key bytes"); + let fvk = FullViewingKey::from(&sk); + let ask = SpendAuthorizingKey::from(&sk); + + let result = build_shielded_withdrawal_transition( + spends, + 100, + CoreScript::new_p2pkh([1u8; 20]), // minimal P2PKH prefix + 1, + Pooling::Never, + &change_address, + &fvk, + &ask, + Anchor::empty_tree(), + proving_key(), + [0u8; 36], + Some(1), // fee = 1, should be below minimum + platform_version, + ); + + assert!(result.is_err()); + let err = result.unwrap_err().to_string(); + assert!( + err.contains("below minimum required fee"), + "unexpected error: {}", + err + ); + } + + #[test] + fn test_shielded_withdrawal_insufficient_funds() { + let platform_version = PlatformVersion::latest(); + let change_address = test_orchard_address(); + + let note = test_spendable_note(100); + let spends = vec![note]; + + let sk = grovedb_commitment_tree::SpendingKey::from_bytes([42u8; 32]) + .expect("valid spending key bytes"); + let fvk = FullViewingKey::from(&sk); + let ask = SpendAuthorizingKey::from(&sk); + + let result = build_shielded_withdrawal_transition( + spends, + 1_000_000, + CoreScript::new_p2pkh([1u8; 20]), + 1, + Pooling::Never, + &change_address, + &fvk, + &ask, + Anchor::empty_tree(), + proving_key(), + [0u8; 36], + None, + platform_version, + ); + + assert!(result.is_err()); + let err = result.unwrap_err().to_string(); + assert!( + err.contains("exceeds total spendable value"), + "unexpected error: {}", + err + ); + } +} diff --git a/packages/rs-dpp/src/shielded/builder/unshield.rs b/packages/rs-dpp/src/shielded/builder/unshield.rs new file mode 100644 index 00000000000..c8842dd0c71 --- /dev/null +++ b/packages/rs-dpp/src/shielded/builder/unshield.rs @@ -0,0 +1,185 @@ +use grovedb_commitment_tree::{Anchor, FullViewingKey, ProvingKey, SpendAuthorizingKey}; + +use crate::address_funds::{OrchardAddress, PlatformAddress}; +use crate::fee::Credits; +use crate::shielded::compute_minimum_shielded_fee; +use crate::state_transition::unshield_transition::methods::UnshieldTransitionMethodsV0; +use crate::state_transition::unshield_transition::UnshieldTransition; +use crate::state_transition::StateTransition; +use crate::ProtocolError; +use platform_version::version::PlatformVersion; + +use super::{build_spend_bundle, serialize_authorized_bundle, SpendableNote}; + +/// Builds an Unshield state transition (shielded pool -> platform address). +/// +/// Spends existing notes and sends part of the value to a transparent platform +/// address. The shielded fee is deducted from the spent notes. Any remaining +/// value is returned to the shielded `change_address`. +/// +/// # Parameters +/// - `spends` - Notes to spend with their Merkle paths +/// - `output_address` - Platform address to receive the unshielded funds +/// - `unshield_amount` - Amount to unshield to the platform address +/// - `change_address` - Orchard address for change output +/// - `fvk` - Full viewing key for spend authorization +/// - `ask` - Spend authorizing key for RedPallas signatures +/// - `anchor` - Sinsemilla root of the note commitment tree (Orchard Anchor) +/// - `proving_key` - Halo 2 proving key +/// - `memo` - 36-byte structured memo for the change output (4-byte type tag + 32-byte payload) +/// - `fee` - Optional fee override; if `None`, the minimum fee is computed automatically. +/// If `Some`, must be >= the minimum fee. +/// - `platform_version` - Protocol version +#[allow(clippy::too_many_arguments)] +pub fn build_unshield_transition( + spends: Vec, + output_address: PlatformAddress, + unshield_amount: u64, + change_address: &OrchardAddress, + fvk: &FullViewingKey, + ask: &SpendAuthorizingKey, + anchor: Anchor, + proving_key: &ProvingKey, + memo: [u8; 36], + fee: Option, + platform_version: &PlatformVersion, +) -> Result { + let total_spent: u64 = spends.iter().map(|s| s.note.value().inner()).sum(); + + // Conservative action count: at least (spends, 1) since we have a change output. + let num_actions = spends.len().max(1); + let min_fee = compute_minimum_shielded_fee(num_actions, platform_version); + let effective_fee = match fee { + Some(f) if f < min_fee => { + return Err(ProtocolError::Generic(format!( + "fee {} is below minimum required fee {}", + f, min_fee + ))); + } + Some(f) => f, + None => min_fee, + }; + + let required = unshield_amount + .checked_add(effective_fee) + .ok_or_else(|| ProtocolError::Generic("fee + unshield_amount overflows u64".to_string()))?; + if required > total_spent { + return Err(ProtocolError::Generic(format!( + "unshield amount {} + fee {} = {} exceeds total spendable value {}", + unshield_amount, effective_fee, required, total_spent + ))); + } + + let change_amount = total_spent - required; + + // Unshield extra_data = output_address.to_bytes() || amount.to_le_bytes() + let mut extra_sighash_data = output_address.to_bytes(); + extra_sighash_data.extend_from_slice(&unshield_amount.to_le_bytes()); + + let bundle = build_spend_bundle( + spends, + change_address, + change_amount, + memo, + fvk, + ask, + anchor, + proving_key, + &extra_sighash_data, + )?; + + let sb = serialize_authorized_bundle(&bundle); + + UnshieldTransition::try_from_bundle( + output_address, + unshield_amount, + sb.actions, + sb.value_balance, + sb.anchor, + sb.proof, + sb.binding_signature, + platform_version, + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::shielded::builder::test_helpers::{ + proving_key, test_orchard_address, test_spendable_note, + }; + + #[test] + fn test_unshield_fee_below_minimum() { + let platform_version = PlatformVersion::latest(); + let change_address = test_orchard_address(); + let output_address = PlatformAddress::P2pkh([1u8; 20]); + + let note = test_spendable_note(1_000_000); + let spends = vec![note]; + + let sk = grovedb_commitment_tree::SpendingKey::from_bytes([42u8; 32]) + .expect("valid spending key bytes"); + let fvk = FullViewingKey::from(&sk); + let ask = SpendAuthorizingKey::from(&sk); + + let result = build_unshield_transition( + spends, + output_address, + 100, + &change_address, + &fvk, + &ask, + Anchor::empty_tree(), + proving_key(), + [0u8; 36], + Some(1), // fee = 1, should be below minimum + platform_version, + ); + + assert!(result.is_err()); + let err = result.unwrap_err().to_string(); + assert!( + err.contains("below minimum required fee"), + "unexpected error: {}", + err + ); + } + + #[test] + fn test_unshield_insufficient_funds() { + let platform_version = PlatformVersion::latest(); + let change_address = test_orchard_address(); + let output_address = PlatformAddress::P2pkh([1u8; 20]); + + let note = test_spendable_note(100); + let spends = vec![note]; + + let sk = grovedb_commitment_tree::SpendingKey::from_bytes([42u8; 32]) + .expect("valid spending key bytes"); + let fvk = FullViewingKey::from(&sk); + let ask = SpendAuthorizingKey::from(&sk); + + let result = build_unshield_transition( + spends, + output_address, + 1_000_000, + &change_address, + &fvk, + &ask, + Anchor::empty_tree(), + proving_key(), + [0u8; 36], + None, + platform_version, + ); + + assert!(result.is_err()); + let err = result.unwrap_err().to_string(); + assert!( + err.contains("exceeds total spendable value"), + "unexpected error: {}", + err + ); + } +} From aee514797d7e5bfdc9323ab87745d165d1ee8d3c Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 5 Mar 2026 13:29:46 +0700 Subject: [PATCH 14/30] refactor(dpp): add OrchardProver trait, rename shield value_balance to amount, add fee cap - Introduce OrchardProver trait abstracting over Halo 2 proving key access, following the same pattern as Signer - Change ShieldTransitionV0.value_balance (i64) to amount (u64) since shield can only flow credits into the pool - Add 1000x minimum fee cap check in shielded transfer builder - Use letter generics across all builder functions Co-Authored-By: Claude Opus 4.6 --- packages/rs-dpp/src/shielded/builder/mod.rs | 39 ++++++++++++++----- .../rs-dpp/src/shielded/builder/shield.rs | 22 ++++------- .../builder/shield_from_asset_lock.rs | 15 +++---- .../src/shielded/builder/shielded_transfer.rs | 22 +++++++---- .../shielded/builder/shielded_withdrawal.rs | 16 ++++---- .../rs-dpp/src/shielded/builder/unshield.rs | 16 ++++---- .../shielded/shield_transition/methods/mod.rs | 4 +- .../shield_transition/methods/v0/mod.rs | 2 +- .../shielded/shield_transition/v0/mod.rs | 6 +-- .../v0/state_transition_validation.rs | 7 ++-- .../shield_transition/v0/v0_methods.rs | 4 +- 11 files changed, 84 insertions(+), 69 deletions(-) diff --git a/packages/rs-dpp/src/shielded/builder/mod.rs b/packages/rs-dpp/src/shielded/builder/mod.rs index 84558c4b5e6..5902c9ce405 100644 --- a/packages/rs-dpp/src/shielded/builder/mod.rs +++ b/packages/rs-dpp/src/shielded/builder/mod.rs @@ -34,7 +34,7 @@ mod shielded_transfer; mod shielded_withdrawal; mod unshield; -pub use shield::build_shield_transition; +pub use self::shield::build_shield_transition; pub use shield_from_asset_lock::build_shield_from_asset_lock_transition; pub use shielded_transfer::build_shielded_transfer_transition; pub use shielded_withdrawal::build_shielded_withdrawal_transition; @@ -50,6 +50,16 @@ use crate::address_funds::OrchardAddress; use crate::shielded::{compute_platform_sighash, SerializedAction}; use crate::ProtocolError; +/// Trait abstracting over Orchard proof generation. +/// +/// This follows the same pattern as `Signer` — callers provide an implementation +/// that holds (and potentially caches) the expensive `ProvingKey`, and the builder +/// functions use it via this trait. +pub trait OrchardProver { + /// Returns a reference to the Halo 2 proving key for the Orchard circuit. + fn proving_key(&self) -> &ProvingKey; +} + /// A note that can be spent in a shielded transaction, paired with its /// Merkle inclusion path in the commitment tree. pub struct SpendableNote { @@ -129,11 +139,11 @@ pub fn serialize_authorized_bundle(bundle: &Bundle) - /// /// Used by Shield and ShieldFromAssetLock transitions where funds enter /// the shielded pool from transparent sources. -pub(crate) fn build_output_only_bundle( +pub(crate) fn build_output_only_bundle( recipient: &OrchardAddress, amount: u64, memo: [u8; 36], - proving_key: &ProvingKey, + prover: &P, ) -> Result, ProtocolError> { let payment_address = PaymentAddress::from(recipient); let anchor = Anchor::empty_tree(); @@ -149,7 +159,7 @@ pub(crate) fn build_output_only_bundle( .add_output(None, payment_address, NoteValue::from_raw(amount), memo) .map_err(|e| ProtocolError::Generic(format!("failed to add output: {:?}", e)))?; - prove_and_sign_bundle(builder, proving_key, &[], &[]) + prove_and_sign_bundle(builder, prover, &[], &[]) } /// Builds a spend+output Orchard bundle. @@ -157,7 +167,7 @@ pub(crate) fn build_output_only_bundle( /// Used by ShieldedTransfer, Unshield, and ShieldedWithdrawal where funds /// are spent from existing notes. #[allow(clippy::too_many_arguments)] -pub(crate) fn build_spend_bundle( +pub(crate) fn build_spend_bundle( spends: Vec, recipient: &OrchardAddress, output_amount: u64, @@ -165,7 +175,7 @@ pub(crate) fn build_spend_bundle( fvk: &FullViewingKey, ask: &SpendAuthorizingKey, anchor: Anchor, - proving_key: &ProvingKey, + prover: &P, extra_sighash_data: &[u8], ) -> Result, ProtocolError> { let payment_address = PaymentAddress::from(recipient); @@ -189,7 +199,7 @@ pub(crate) fn build_spend_bundle( prove_and_sign_bundle( builder, - proving_key, + prover, std::slice::from_ref(ask), extra_sighash_data, ) @@ -197,9 +207,9 @@ pub(crate) fn build_spend_bundle( /// Takes a configured Builder, generates the proof, computes the platform /// sighash, and applies signatures. -pub(crate) fn prove_and_sign_bundle( +pub(crate) fn prove_and_sign_bundle( builder: Builder, - proving_key: &ProvingKey, + prover: &P, signing_keys: &[SpendAuthorizingKey], extra_sighash_data: &[u8], ) -> Result, ProtocolError> { @@ -214,7 +224,7 @@ pub(crate) fn prove_and_sign_bundle( let sighash = compute_platform_sighash(&bundle_commitment, extra_sighash_data); let proven = unauthorized - .create_proof(proving_key, &mut rng) + .create_proof(prover.proving_key(), &mut rng) .map_err(|e| ProtocolError::Generic(format!("failed to create proof: {:?}", e)))?; proven @@ -239,6 +249,15 @@ pub(crate) mod test_helpers { PROVING_KEY.get_or_init(ProvingKey::build) } + /// Test implementation of `OrchardProver` backed by the cached proving key. + pub struct TestProver; + + impl super::OrchardProver for TestProver { + fn proving_key(&self) -> &ProvingKey { + proving_key() + } + } + /// Creates a test OrchardAddress from a deterministic spending key. pub fn test_orchard_address() -> OrchardAddress { let sk = SpendingKey::from_bytes([42u8; 32]).expect("valid spending key bytes"); diff --git a/packages/rs-dpp/src/shielded/builder/shield.rs b/packages/rs-dpp/src/shielded/builder/shield.rs index bc5f0d288ed..5f8ad7603f7 100644 --- a/packages/rs-dpp/src/shielded/builder/shield.rs +++ b/packages/rs-dpp/src/shielded/builder/shield.rs @@ -1,7 +1,5 @@ use std::collections::BTreeMap; -use grovedb_commitment_tree::ProvingKey; - use crate::address_funds::AddressFundsFeeStrategy; use crate::address_funds::{OrchardAddress, PlatformAddress}; use crate::fee::Credits; @@ -13,7 +11,7 @@ use crate::state_transition::StateTransition; use crate::ProtocolError; use platform_version::version::PlatformVersion; -use super::{build_output_only_bundle, serialize_authorized_bundle}; +use super::{build_output_only_bundle, serialize_authorized_bundle, OrchardProver}; /// Builds a Shield state transition (transparent platform addresses -> shielded pool). /// @@ -31,14 +29,14 @@ use super::{build_output_only_bundle, serialize_authorized_bundle}; /// - `memo` - 36-byte structured memo for the recipient (4-byte type tag + 32-byte payload) /// - `platform_version` - Protocol version #[allow(clippy::too_many_arguments)] -pub fn build_shield_transition>( +pub fn build_shield_transition, P: OrchardProver>( recipient: &OrchardAddress, shield_amount: u64, inputs: BTreeMap, fee_strategy: AddressFundsFeeStrategy, signer: &S, user_fee_increase: UserFeeIncrease, - proving_key: &ProvingKey, + prover: &P, memo: [u8; 36], platform_version: &PlatformVersion, ) -> Result { @@ -48,13 +46,13 @@ pub fn build_shield_transition>( )); } - let bundle = build_output_only_bundle(recipient, shield_amount, memo, proving_key)?; + let bundle = build_output_only_bundle(recipient, shield_amount, memo, prover)?; let sb = serialize_authorized_bundle(&bundle); ShieldTransition::try_from_bundle_with_signer( inputs, sb.actions, - sb.value_balance, + sb.value_balance.unsigned_abs(), sb.anchor, sb.proof, sb.binding_signature, @@ -70,7 +68,7 @@ mod tests { use super::*; use crate::address_funds::AddressWitness; use crate::address_funds::AddressFundsFeeStrategyStep; - use crate::shielded::builder::test_helpers::{proving_key, test_orchard_address}; + use crate::shielded::builder::test_helpers::{test_orchard_address, TestProver}; use platform_value::BinaryData; /// A dummy signer that produces a fake 65-byte signature. @@ -102,8 +100,6 @@ mod tests { fn test_build_shield_empty_fee_strategy() { let recipient = test_orchard_address(); let platform_version = PlatformVersion::latest(); - let pk = proving_key(); - let result = build_shield_transition( &recipient, 1000, @@ -111,7 +107,7 @@ mod tests { vec![], // empty fee strategy &DummySigner, 0, - pk, + &TestProver, [0u8; 36], platform_version, ); @@ -129,8 +125,6 @@ mod tests { fn test_build_shield_transition_valid() { let recipient = test_orchard_address(); let platform_version = PlatformVersion::latest(); - let pk = proving_key(); - // Create a P2PKH address as input let input_address = PlatformAddress::P2pkh([1u8; 20]); let mut inputs = BTreeMap::new(); @@ -145,7 +139,7 @@ mod tests { fee_strategy, &DummySigner, 0, - pk, + &TestProver, [0u8; 36], platform_version, ); diff --git a/packages/rs-dpp/src/shielded/builder/shield_from_asset_lock.rs b/packages/rs-dpp/src/shielded/builder/shield_from_asset_lock.rs index 0758c052456..e2a59af3688 100644 --- a/packages/rs-dpp/src/shielded/builder/shield_from_asset_lock.rs +++ b/packages/rs-dpp/src/shielded/builder/shield_from_asset_lock.rs @@ -1,5 +1,3 @@ -use grovedb_commitment_tree::ProvingKey; - use crate::address_funds::OrchardAddress; use crate::prelude::{AssetLockProof, UserFeeIncrease}; use crate::state_transition::shield_from_asset_lock_transition::methods::ShieldFromAssetLockTransitionMethodsV0; @@ -8,7 +6,7 @@ use crate::state_transition::StateTransition; use crate::ProtocolError; use platform_version::version::PlatformVersion; -use super::{build_output_only_bundle, serialize_authorized_bundle}; +use super::{build_output_only_bundle, serialize_authorized_bundle, OrchardProver}; /// Builds a ShieldFromAssetLock state transition (core asset lock -> shielded pool). /// @@ -25,17 +23,17 @@ use super::{build_output_only_bundle, serialize_authorized_bundle}; /// - `memo` - 36-byte structured memo for the recipient (4-byte type tag + 32-byte payload) /// - `platform_version` - Protocol version #[allow(clippy::too_many_arguments)] -pub fn build_shield_from_asset_lock_transition( +pub fn build_shield_from_asset_lock_transition( recipient: &OrchardAddress, shield_amount: u64, asset_lock_proof: AssetLockProof, asset_lock_private_key: &[u8], user_fee_increase: UserFeeIncrease, - proving_key: &ProvingKey, + prover: &P, memo: [u8; 36], platform_version: &PlatformVersion, ) -> Result { - let bundle = build_output_only_bundle(recipient, shield_amount, memo, proving_key)?; + let bundle = build_output_only_bundle(recipient, shield_amount, memo, prover)?; let sb = serialize_authorized_bundle(&bundle); // For output-only bundles, Orchard value_balance is negative (value flowing in). @@ -66,7 +64,7 @@ pub fn build_shield_from_asset_lock_transition( #[cfg(test)] mod tests { use super::super::{build_output_only_bundle, serialize_authorized_bundle}; - use crate::shielded::builder::test_helpers::{proving_key, test_orchard_address}; + use crate::shielded::builder::test_helpers::{test_orchard_address, TestProver}; /// Verifies that an output-only bundle produces a negative value_balance /// (value flowing into the pool), which is the precondition for @@ -74,10 +72,9 @@ mod tests { #[test] fn test_output_only_bundle_value_balance_is_negative() { let recipient = test_orchard_address(); - let pk = proving_key(); let amount = 50_000u64; - let bundle = build_output_only_bundle(&recipient, amount, [0u8; 36], pk) + let bundle = build_output_only_bundle(&recipient, amount, [0u8; 36], &TestProver) .expect("bundle should build successfully"); let sb = serialize_authorized_bundle(&bundle); diff --git a/packages/rs-dpp/src/shielded/builder/shielded_transfer.rs b/packages/rs-dpp/src/shielded/builder/shielded_transfer.rs index 7efa94db9ad..cea22dc7384 100644 --- a/packages/rs-dpp/src/shielded/builder/shielded_transfer.rs +++ b/packages/rs-dpp/src/shielded/builder/shielded_transfer.rs @@ -1,5 +1,5 @@ use grovedb_commitment_tree::{ - Anchor, Builder, BundleType, DashMemo, FullViewingKey, NoteValue, PaymentAddress, ProvingKey, + Anchor, Builder, BundleType, DashMemo, FullViewingKey, NoteValue, PaymentAddress, SpendAuthorizingKey, }; @@ -12,7 +12,7 @@ use crate::state_transition::StateTransition; use crate::ProtocolError; use platform_version::version::PlatformVersion; -use super::{prove_and_sign_bundle, serialize_authorized_bundle, SpendableNote}; +use super::{prove_and_sign_bundle, serialize_authorized_bundle, OrchardProver, SpendableNote}; /// Builds a ShieldedTransfer state transition (shielded pool -> shielded pool). /// @@ -34,7 +34,7 @@ use super::{prove_and_sign_bundle, serialize_authorized_bundle, SpendableNote}; /// If `Some`, must be >= the minimum fee. /// - `platform_version` - Protocol version #[allow(clippy::too_many_arguments)] -pub fn build_shielded_transfer_transition( +pub fn build_shielded_transfer_transition( spends: Vec, recipient: &OrchardAddress, transfer_amount: u64, @@ -42,7 +42,7 @@ pub fn build_shielded_transfer_transition( fvk: &FullViewingKey, ask: &SpendAuthorizingKey, anchor: Anchor, - proving_key: &ProvingKey, + prover: &P, memo: [u8; 36], fee: Option, platform_version: &PlatformVersion, @@ -60,6 +60,12 @@ pub fn build_shielded_transfer_transition( f, min_fee ))); } + Some(f) if f > min_fee.saturating_mul(1000) => { + return Err(ProtocolError::Generic(format!( + "fee {} exceeds 1000x the minimum fee {}", + f, min_fee + ))); + } Some(f) => f, None => min_fee, }; @@ -110,7 +116,7 @@ pub fn build_shielded_transfer_transition( } // ShieldedTransfer has no extra_data in sighash - let bundle = prove_and_sign_bundle(builder, proving_key, std::slice::from_ref(ask), &[])?; + let bundle = prove_and_sign_bundle(builder, prover, std::slice::from_ref(ask), &[])?; let sb = serialize_authorized_bundle(&bundle); // value_balance = effective_fee (the amount leaving the shielded pool as fee) @@ -128,7 +134,7 @@ pub fn build_shielded_transfer_transition( mod tests { use super::*; use crate::shielded::builder::test_helpers::{ - proving_key, test_orchard_address, test_spendable_note, + test_orchard_address, test_spendable_note, TestProver, }; #[test] @@ -153,7 +159,7 @@ mod tests { &fvk, &ask, Anchor::empty_tree(), - proving_key(), + &TestProver, [0u8; 36], Some(1), // fee = 1, should be below minimum platform_version, @@ -191,7 +197,7 @@ mod tests { &fvk, &ask, Anchor::empty_tree(), - proving_key(), + &TestProver, [0u8; 36], None, platform_version, diff --git a/packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs b/packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs index 0f4a795d561..a9e4f7fbbaa 100644 --- a/packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs +++ b/packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs @@ -1,4 +1,4 @@ -use grovedb_commitment_tree::{Anchor, FullViewingKey, ProvingKey, SpendAuthorizingKey}; +use grovedb_commitment_tree::{Anchor, FullViewingKey, SpendAuthorizingKey}; use crate::address_funds::OrchardAddress; use crate::fee::Credits; @@ -11,7 +11,7 @@ use crate::withdrawal::Pooling; use crate::ProtocolError; use platform_version::version::PlatformVersion; -use super::{build_spend_bundle, serialize_authorized_bundle, SpendableNote}; +use super::{build_spend_bundle, serialize_authorized_bundle, OrchardProver, SpendableNote}; /// Builds a ShieldedWithdrawal state transition (shielded pool -> core L1 address). /// @@ -35,7 +35,7 @@ use super::{build_spend_bundle, serialize_authorized_bundle, SpendableNote}; /// If `Some`, must be >= the minimum fee. /// - `platform_version` - Protocol version #[allow(clippy::too_many_arguments)] -pub fn build_shielded_withdrawal_transition( +pub fn build_shielded_withdrawal_transition( spends: Vec, withdrawal_amount: u64, output_script: CoreScript, @@ -45,7 +45,7 @@ pub fn build_shielded_withdrawal_transition( fvk: &FullViewingKey, ask: &SpendAuthorizingKey, anchor: Anchor, - proving_key: &ProvingKey, + prover: &P, memo: [u8; 36], fee: Option, platform_version: &PlatformVersion, @@ -92,7 +92,7 @@ pub fn build_shielded_withdrawal_transition( fvk, ask, anchor, - proving_key, + prover, &extra_sighash_data, )?; @@ -116,7 +116,7 @@ pub fn build_shielded_withdrawal_transition( mod tests { use super::*; use crate::shielded::builder::test_helpers::{ - proving_key, test_orchard_address, test_spendable_note, + test_orchard_address, test_spendable_note, TestProver, }; #[test] @@ -142,7 +142,7 @@ mod tests { &fvk, &ask, Anchor::empty_tree(), - proving_key(), + &TestProver, [0u8; 36], Some(1), // fee = 1, should be below minimum platform_version, @@ -180,7 +180,7 @@ mod tests { &fvk, &ask, Anchor::empty_tree(), - proving_key(), + &TestProver, [0u8; 36], None, platform_version, diff --git a/packages/rs-dpp/src/shielded/builder/unshield.rs b/packages/rs-dpp/src/shielded/builder/unshield.rs index c8842dd0c71..d7f232ed65b 100644 --- a/packages/rs-dpp/src/shielded/builder/unshield.rs +++ b/packages/rs-dpp/src/shielded/builder/unshield.rs @@ -1,4 +1,4 @@ -use grovedb_commitment_tree::{Anchor, FullViewingKey, ProvingKey, SpendAuthorizingKey}; +use grovedb_commitment_tree::{Anchor, FullViewingKey, SpendAuthorizingKey}; use crate::address_funds::{OrchardAddress, PlatformAddress}; use crate::fee::Credits; @@ -9,7 +9,7 @@ use crate::state_transition::StateTransition; use crate::ProtocolError; use platform_version::version::PlatformVersion; -use super::{build_spend_bundle, serialize_authorized_bundle, SpendableNote}; +use super::{build_spend_bundle, serialize_authorized_bundle, OrchardProver, SpendableNote}; /// Builds an Unshield state transition (shielded pool -> platform address). /// @@ -31,7 +31,7 @@ use super::{build_spend_bundle, serialize_authorized_bundle, SpendableNote}; /// If `Some`, must be >= the minimum fee. /// - `platform_version` - Protocol version #[allow(clippy::too_many_arguments)] -pub fn build_unshield_transition( +pub fn build_unshield_transition( spends: Vec, output_address: PlatformAddress, unshield_amount: u64, @@ -39,7 +39,7 @@ pub fn build_unshield_transition( fvk: &FullViewingKey, ask: &SpendAuthorizingKey, anchor: Anchor, - proving_key: &ProvingKey, + prover: &P, memo: [u8; 36], fee: Option, platform_version: &PlatformVersion, @@ -84,7 +84,7 @@ pub fn build_unshield_transition( fvk, ask, anchor, - proving_key, + prover, &extra_sighash_data, )?; @@ -106,7 +106,7 @@ pub fn build_unshield_transition( mod tests { use super::*; use crate::shielded::builder::test_helpers::{ - proving_key, test_orchard_address, test_spendable_note, + test_orchard_address, test_spendable_note, TestProver, }; #[test] @@ -131,7 +131,7 @@ mod tests { &fvk, &ask, Anchor::empty_tree(), - proving_key(), + &TestProver, [0u8; 36], Some(1), // fee = 1, should be below minimum platform_version, @@ -168,7 +168,7 @@ mod tests { &fvk, &ask, Anchor::empty_tree(), - proving_key(), + &TestProver, [0u8; 36], None, platform_version, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/methods/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/methods/mod.rs index 6d11ed71562..d87fb75333d 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/methods/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/methods/mod.rs @@ -27,7 +27,7 @@ impl ShieldTransitionMethodsV0 for ShieldTransition { fn try_from_bundle_with_signer>( inputs: BTreeMap, actions: Vec, - value_balance: i64, + amount: u64, anchor: [u8; 32], proof: Vec, binding_signature: [u8; 64], @@ -45,7 +45,7 @@ impl ShieldTransitionMethodsV0 for ShieldTransition { 0 => ShieldTransitionV0::try_from_bundle_with_signer( inputs, actions, - value_balance, + amount, anchor, proof, binding_signature, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/methods/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/methods/v0/mod.rs index 05a88aed176..ea69b2a435a 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/methods/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/methods/v0/mod.rs @@ -25,7 +25,7 @@ pub trait ShieldTransitionMethodsV0 { fn try_from_bundle_with_signer>( inputs: BTreeMap, actions: Vec, - value_balance: i64, + amount: u64, anchor: [u8; 32], proof: Vec, binding_signature: [u8; 64], diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/mod.rs index c8d04c2074a..2ad25b20f55 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/mod.rs @@ -40,8 +40,8 @@ pub struct ShieldTransitionV0 { pub inputs: BTreeMap, /// Orchard actions (spend-output pairs) pub actions: Vec, - /// Net value flowing into/out of the shielded pool - pub value_balance: i64, + /// Amount of credits being shielded (entering the shielded pool). + pub amount: u64, /// Sinsemilla root of the note commitment tree (Orchard Anchor) pub anchor: [u8; 32], /// Halo2 proof bytes @@ -96,7 +96,7 @@ mod tests { cv_net: [5u8; 32], spend_auth_sig: [6u8; 64], }], - value_balance: -1000i64, + amount: 1000u64, anchor: [7u8; 32], proof: vec![8u8; 100], binding_signature: [9u8; 64], 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 0aaf4f058b7..9240ff676e8 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 @@ -64,13 +64,12 @@ impl StateTransitionStructureValidation for ShieldTransitionV0 { } } - // value_balance must be negative (credits flowing into pool) - if self.value_balance >= 0 { + // amount must be positive (credits flowing into pool) + if self.amount == 0 { return SimpleConsensusValidationResult::new_with_error( BasicError::ShieldedInvalidValueBalanceError( ShieldedInvalidValueBalanceError::new( - "shield value_balance must be negative (credits flow into pool)" - .to_string(), + "shield amount must be greater than zero".to_string(), ), ) .into(), diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/v0_methods.rs index e8d41ad4dd1..28565600d97 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/v0_methods.rs @@ -27,7 +27,7 @@ impl ShieldTransitionMethodsV0 for ShieldTransitionV0 { fn try_from_bundle_with_signer>( inputs: BTreeMap, actions: Vec, - value_balance: i64, + amount: u64, anchor: [u8; 32], proof: Vec, binding_signature: [u8; 64], @@ -40,7 +40,7 @@ impl ShieldTransitionMethodsV0 for ShieldTransitionV0 { let mut shield_transition = ShieldTransitionV0 { inputs: inputs.clone(), actions, - value_balance, + amount, anchor, proof, binding_signature, From 89072a9e271b3b4a83edfc6fd3a6244706fca703 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 5 Mar 2026 13:30:13 +0700 Subject: [PATCH 15/30] docs(dpp): update proving_key doc comments to reference prover param Co-Authored-By: Claude Opus 4.6 --- packages/rs-dpp/src/shielded/builder/shield.rs | 2 +- packages/rs-dpp/src/shielded/builder/shield_from_asset_lock.rs | 2 +- packages/rs-dpp/src/shielded/builder/shielded_transfer.rs | 2 +- packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs | 2 +- packages/rs-dpp/src/shielded/builder/unshield.rs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/rs-dpp/src/shielded/builder/shield.rs b/packages/rs-dpp/src/shielded/builder/shield.rs index 5f8ad7603f7..08ae57c7ca0 100644 --- a/packages/rs-dpp/src/shielded/builder/shield.rs +++ b/packages/rs-dpp/src/shielded/builder/shield.rs @@ -25,7 +25,7 @@ use super::{build_output_only_bundle, serialize_authorized_bundle, OrchardProver /// - `fee_strategy` - How to deduct fees from the transparent inputs /// - `signer` - Signs each input address witness (ECDSA) /// - `user_fee_increase` - Fee multiplier (0 = 100% base fee) -/// - `proving_key` - Halo 2 proving key (cache with `OnceLock` — ~30s to build) +/// - `prover` - Orchard prover (holds the Halo 2 proving key; cache with `OnceLock` — ~30s to build) /// - `memo` - 36-byte structured memo for the recipient (4-byte type tag + 32-byte payload) /// - `platform_version` - Protocol version #[allow(clippy::too_many_arguments)] diff --git a/packages/rs-dpp/src/shielded/builder/shield_from_asset_lock.rs b/packages/rs-dpp/src/shielded/builder/shield_from_asset_lock.rs index e2a59af3688..8ddfebaa04d 100644 --- a/packages/rs-dpp/src/shielded/builder/shield_from_asset_lock.rs +++ b/packages/rs-dpp/src/shielded/builder/shield_from_asset_lock.rs @@ -19,7 +19,7 @@ use super::{build_output_only_bundle, serialize_authorized_bundle, OrchardProver /// - `asset_lock_proof` - Proof that funds are locked on core chain /// - `asset_lock_private_key` - Private key for the asset lock (signs the transition) /// - `user_fee_increase` - Fee multiplier (0 = 100% base fee) -/// - `proving_key` - Halo 2 proving key +/// - `prover` - Orchard prover (holds the Halo 2 proving key) /// - `memo` - 36-byte structured memo for the recipient (4-byte type tag + 32-byte payload) /// - `platform_version` - Protocol version #[allow(clippy::too_many_arguments)] diff --git a/packages/rs-dpp/src/shielded/builder/shielded_transfer.rs b/packages/rs-dpp/src/shielded/builder/shielded_transfer.rs index cea22dc7384..03217df0cef 100644 --- a/packages/rs-dpp/src/shielded/builder/shielded_transfer.rs +++ b/packages/rs-dpp/src/shielded/builder/shielded_transfer.rs @@ -28,7 +28,7 @@ use super::{prove_and_sign_bundle, serialize_authorized_bundle, OrchardProver, S /// - `fvk` - Full viewing key for spend authorization /// - `ask` - Spend authorizing key for RedPallas signatures /// - `anchor` - Sinsemilla root of the note commitment tree (Orchard Anchor) -/// - `proving_key` - Halo 2 proving key +/// - `prover` - Orchard prover (holds the Halo 2 proving key) /// - `memo` - 36-byte structured memo for the recipient (4-byte type tag + 32-byte payload) /// - `fee` - Optional fee override; if `None`, the minimum fee is computed automatically. /// If `Some`, must be >= the minimum fee. diff --git a/packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs b/packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs index a9e4f7fbbaa..afaaffb7ff9 100644 --- a/packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs +++ b/packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs @@ -29,7 +29,7 @@ use super::{build_spend_bundle, serialize_authorized_bundle, OrchardProver, Spen /// - `fvk` - Full viewing key for spend authorization /// - `ask` - Spend authorizing key for RedPallas signatures /// - `anchor` - Sinsemilla root of the note commitment tree (Orchard Anchor) -/// - `proving_key` - Halo 2 proving key +/// - `prover` - Orchard prover (holds the Halo 2 proving key) /// - `memo` - 36-byte structured memo for the change output (4-byte type tag + 32-byte payload) /// - `fee` - Optional fee override; if `None`, the minimum fee is computed automatically. /// If `Some`, must be >= the minimum fee. diff --git a/packages/rs-dpp/src/shielded/builder/unshield.rs b/packages/rs-dpp/src/shielded/builder/unshield.rs index d7f232ed65b..2cac1215a53 100644 --- a/packages/rs-dpp/src/shielded/builder/unshield.rs +++ b/packages/rs-dpp/src/shielded/builder/unshield.rs @@ -25,7 +25,7 @@ use super::{build_spend_bundle, serialize_authorized_bundle, OrchardProver, Spen /// - `fvk` - Full viewing key for spend authorization /// - `ask` - Spend authorizing key for RedPallas signatures /// - `anchor` - Sinsemilla root of the note commitment tree (Orchard Anchor) -/// - `proving_key` - Halo 2 proving key +/// - `prover` - Orchard prover (holds the Halo 2 proving key) /// - `memo` - 36-byte structured memo for the change output (4-byte type tag + 32-byte payload) /// - `fee` - Optional fee override; if `None`, the minimum fee is computed automatically. /// If `Some`, must be >= the minimum fee. From 7d5fd72b1e2d82beb660bd33025e3d40de8b811f Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 5 Mar 2026 15:38:10 +0700 Subject: [PATCH 16/30] fix(drive): resolve CI failures in dpp formatting, drive and drive-abci - Fix import ordering in shield.rs tests (dpp formatting) - Handle Option in credit_pools test assertions (drive) - Add create_initial_state_structure v3 stub delegating to v2 (drive-abci) Co-Authored-By: Claude Opus 4.6 --- packages/rs-dpp/src/shielded/builder/mod.rs | 10 +++++--- .../rs-dpp/src/shielded/builder/shield.rs | 2 +- .../rs-drive/src/drive/credit_pools/mod.rs | 5 +++- .../v0/mod.rs | 2 +- .../rs-drive/src/drive/initialization/mod.rs | 4 +++- .../src/drive/initialization/v3/mod.rs | 23 +++++++++++++++++++ 6 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 packages/rs-drive/src/drive/initialization/v3/mod.rs diff --git a/packages/rs-dpp/src/shielded/builder/mod.rs b/packages/rs-dpp/src/shielded/builder/mod.rs index 5902c9ce405..77105a7926c 100644 --- a/packages/rs-dpp/src/shielded/builder/mod.rs +++ b/packages/rs-dpp/src/shielded/builder/mod.rs @@ -283,9 +283,13 @@ pub(crate) mod test_helpers { Option::from(Rho::from_bytes(&[0u8; 32])).expect("zero is valid pallas::Base"); let rseed: RandomSeed = Option::from(RandomSeed::from_bytes([1u8; 32], &rho)).expect("valid random seed"); - let note: Note = - Option::from(Note::from_parts(payment_address, NoteValue::from_raw(value), rho, rseed)) - .expect("note commitment should be valid"); + let note: Note = Option::from(Note::from_parts( + payment_address, + NoteValue::from_raw(value), + rho, + rseed, + )) + .expect("note commitment should be valid"); // All-zeros merkle path at position 0 — consistent with Anchor::empty_tree() let auth_path = [MerkleHashOrchard::empty_leaf(); NOTE_COMMITMENT_TREE_DEPTH]; diff --git a/packages/rs-dpp/src/shielded/builder/shield.rs b/packages/rs-dpp/src/shielded/builder/shield.rs index 08ae57c7ca0..3f41b67ded3 100644 --- a/packages/rs-dpp/src/shielded/builder/shield.rs +++ b/packages/rs-dpp/src/shielded/builder/shield.rs @@ -66,8 +66,8 @@ pub fn build_shield_transition, P: OrchardProver>( #[cfg(test)] mod tests { use super::*; - use crate::address_funds::AddressWitness; use crate::address_funds::AddressFundsFeeStrategyStep; + use crate::address_funds::AddressWitness; use crate::shielded::builder::test_helpers::{test_orchard_address, TestProver}; use platform_value::BinaryData; diff --git a/packages/rs-drive/src/drive/credit_pools/mod.rs b/packages/rs-drive/src/drive/credit_pools/mod.rs index e43660706d0..6d48db1634b 100644 --- a/packages/rs-drive/src/drive/credit_pools/mod.rs +++ b/packages/rs-drive/src/drive/credit_pools/mod.rs @@ -260,7 +260,10 @@ mod tests { assert_eq!(batch.len(), TO_EPOCH_INDEX as usize); for (i, operation) in batch.into_iter().enumerate() { - assert_eq!(operation.key.get_key(), KEY_POOL_STORAGE_FEES); + assert_eq!( + operation.key.expect("key should be present").get_key(), + KEY_POOL_STORAGE_FEES + ); assert_eq!( operation.path.to_path(), diff --git a/packages/rs-drive/src/drive/credit_pools/pending_epoch_refunds/methods/add_delete_pending_epoch_refunds_except_specified/v0/mod.rs b/packages/rs-drive/src/drive/credit_pools/pending_epoch_refunds/methods/add_delete_pending_epoch_refunds_except_specified/v0/mod.rs index 146333b63ce..14107e8d863 100644 --- a/packages/rs-drive/src/drive/credit_pools/pending_epoch_refunds/methods/add_delete_pending_epoch_refunds_except_specified/v0/mod.rs +++ b/packages/rs-drive/src/drive/credit_pools/pending_epoch_refunds/methods/add_delete_pending_epoch_refunds_except_specified/v0/mod.rs @@ -128,7 +128,7 @@ mod tests { assert_eq!(operation.path.to_path(), pending_epoch_refunds_path_vec()); - let epoch_index_key = operation.key.get_key(); + let epoch_index_key = operation.key.expect("key should be present").get_key(); let epoch_index = u16::from_be_bytes( epoch_index_key .try_into() diff --git a/packages/rs-drive/src/drive/initialization/mod.rs b/packages/rs-drive/src/drive/initialization/mod.rs index d9d8add1817..f08df6e01c3 100644 --- a/packages/rs-drive/src/drive/initialization/mod.rs +++ b/packages/rs-drive/src/drive/initialization/mod.rs @@ -4,6 +4,7 @@ mod genesis_core_height; mod v0; mod v1; mod v2; +mod v3; use crate::drive::Drive; use crate::error::drive::DriveError; @@ -28,9 +29,10 @@ impl Drive { 0 => self.create_initial_state_structure_v0(transaction, platform_version), 1 => self.create_initial_state_structure_v1(transaction, platform_version), 2 => self.create_initial_state_structure_v2(transaction, platform_version), + 3 => self.create_initial_state_structure_v3(transaction, platform_version), version => Err(Error::Drive(DriveError::UnknownVersionMismatch { method: "create_initial_state_structure".to_string(), - known_versions: vec![0, 1, 2], + known_versions: vec![0, 1, 2, 3], received: version, })), } diff --git a/packages/rs-drive/src/drive/initialization/v3/mod.rs b/packages/rs-drive/src/drive/initialization/v3/mod.rs new file mode 100644 index 00000000000..c53a438148d --- /dev/null +++ b/packages/rs-drive/src/drive/initialization/v3/mod.rs @@ -0,0 +1,23 @@ +//! Drive Initialization v3 — adds shielded pool trees. + +use crate::drive::Drive; +use crate::error::Error; +use dpp::version::PlatformVersion; +use grovedb::TransactionArg; + +impl Drive { + /// Creates the initial state structure (v3). + /// + /// Extends v2 by initializing shielded pool trees (commitment tree, + /// nullifier set, anchor history). + pub(super) fn create_initial_state_structure_v3( + &self, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result<(), Error> { + // v3 currently delegates to v2. + // Shielded pool tree initialization will be added here once the + // RootTree variants for the shielded pool are defined. + self.create_initial_state_structure_v2(transaction, platform_version) + } +} From babebf0c6edbd91eb1b369fb2396761b3983a2f7 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 5 Mar 2026 16:05:25 +0700 Subject: [PATCH 17/30] fix(dpp): use asset lock proof as unique identifier for ShieldFromAssetLock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The asset lock can only be consumed once, making it the natural deduplication key — consistent with IdentityTopUp. Co-Authored-By: Claude Opus 4.6 --- .../v0/state_transition_like.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/state_transition_like.rs index 19727081167..27f72e2989f 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/state_transition_like.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/state_transition_like.rs @@ -1,3 +1,5 @@ +use base64::prelude::BASE64_STANDARD; +use base64::Engine; use platform_value::BinaryData; use crate::state_transition::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0; @@ -31,9 +33,18 @@ impl StateTransitionLike for ShieldFromAssetLockTransitionV0 { vec![] } - /// Returns unique identifiers based on the cmx values from actions + /// Returns unique identifier based on the asset lock proof. + /// The asset lock can only be consumed once, making it the natural deduplication key. fn unique_identifiers(&self) -> Vec { - self.actions.iter().map(|a| hex::encode(a.cmx)).collect() + let identifier = self.asset_lock_proof.create_identifier(); + match identifier { + Ok(identifier) => { + vec![BASE64_STANDARD.encode(identifier)] + } + Err(_) => { + vec![String::default()] + } + } } } From fa81bdeeb41b20f3f3e538c4674566dc525f7909 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 5 Mar 2026 17:21:18 +0700 Subject: [PATCH 18/30] refactor(dpp): remove redundant `amount` field from Unshield and ShieldedWithdrawal The `amount` is fully derivable from `value_balance - fee` where fee is deterministic. Removing it simplifies the structs, eliminates redundant validation checks, and reduces the sighash extra_data to just the output address/script. The amount is already cryptographically bound through value_balance in the bundle commitment. Co-Authored-By: Claude Opus 4.6 --- .../src/errors/consensus/basic/basic_error.rs | 7 +-- .../consensus/basic/state_transition/mod.rs | 4 -- .../unshield_amount_zero_error.rs | 37 ---------------- ...shield_value_balance_below_amount_error.rs | 44 ------------------- packages/rs-dpp/src/errors/consensus/codes.rs | 2 - .../shielded/builder/shielded_withdrawal.rs | 6 +-- .../rs-dpp/src/shielded/builder/unshield.rs | 6 +-- packages/rs-dpp/src/shielded/mod.rs | 7 +-- .../methods/mod.rs | 2 - .../methods/v0/mod.rs | 1 - .../shielded_withdrawal_transition/v0/mod.rs | 3 -- .../v0/state_transition_validation.rs | 21 +-------- .../v0/v0_methods.rs | 2 - .../unshield_transition/methods/mod.rs | 2 - .../unshield_transition/methods/v0/mod.rs | 1 - .../shielded/unshield_transition/v0/mod.rs | 3 -- .../v0/state_transition_validation.rs | 21 +-------- .../unshield_transition/v0/v0_methods.rs | 2 - .../src/errors/consensus/consensus_error.rs | 8 +--- 19 files changed, 12 insertions(+), 167 deletions(-) delete mode 100644 packages/rs-dpp/src/errors/consensus/basic/state_transition/unshield_amount_zero_error.rs delete mode 100644 packages/rs-dpp/src/errors/consensus/basic/state_transition/unshield_value_balance_below_amount_error.rs 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 be18afa8680..5407735af79 100644 --- a/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs +++ b/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs @@ -81,7 +81,7 @@ use crate::consensus::basic::state_transition::{ ShieldedNoActionsError, ShieldedTooManyActionsError, ShieldedZeroAnchorError, StateTransitionMaxSizeExceededError, StateTransitionNotActiveError, TransitionNoInputsError, TransitionNoOutputsError, TransitionOverMaxInputsError, TransitionOverMaxOutputsError, - UnshieldAmountZeroError, UnshieldValueBalanceBelowAmountError, WithdrawalBalanceMismatchError, + WithdrawalBalanceMismatchError, WithdrawalBelowMinAmountError, }; use crate::consensus::basic::{ @@ -675,11 +675,6 @@ pub enum BasicError { #[error(transparent)] ShieldedInvalidValueBalanceError(ShieldedInvalidValueBalanceError), - #[error(transparent)] - UnshieldAmountZeroError(UnshieldAmountZeroError), - - #[error(transparent)] - UnshieldValueBalanceBelowAmountError(UnshieldValueBalanceBelowAmountError), } 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 a5721bcdd4a..b9acc33f2a7 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 @@ -24,8 +24,6 @@ mod transition_no_inputs_error; mod transition_no_outputs_error; mod transition_over_max_inputs_error; mod transition_over_max_outputs_error; -mod unshield_amount_zero_error; -mod unshield_value_balance_below_amount_error; mod withdrawal_balance_mismatch_error; mod withdrawal_below_min_amount_error; @@ -55,7 +53,5 @@ pub use transition_no_inputs_error::*; pub use transition_no_outputs_error::*; pub use transition_over_max_inputs_error::*; pub use transition_over_max_outputs_error::*; -pub use unshield_amount_zero_error::*; -pub use unshield_value_balance_below_amount_error::*; pub use withdrawal_balance_mismatch_error::*; pub use withdrawal_below_min_amount_error::*; diff --git a/packages/rs-dpp/src/errors/consensus/basic/state_transition/unshield_amount_zero_error.rs b/packages/rs-dpp/src/errors/consensus/basic/state_transition/unshield_amount_zero_error.rs deleted file mode 100644 index 4d0a618c73f..00000000000 --- a/packages/rs-dpp/src/errors/consensus/basic/state_transition/unshield_amount_zero_error.rs +++ /dev/null @@ -1,37 +0,0 @@ -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("Unshield transition amount must be greater than zero")] -#[platform_serialize(unversioned)] -pub struct UnshieldAmountZeroError { - /* - - DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION - - */ -} - -impl UnshieldAmountZeroError { - pub fn new() -> Self { - Self {} - } -} - -impl Default for UnshieldAmountZeroError { - fn default() -> Self { - Self::new() - } -} - -impl From for ConsensusError { - fn from(err: UnshieldAmountZeroError) -> Self { - Self::BasicError(BasicError::UnshieldAmountZeroError(err)) - } -} diff --git a/packages/rs-dpp/src/errors/consensus/basic/state_transition/unshield_value_balance_below_amount_error.rs b/packages/rs-dpp/src/errors/consensus/basic/state_transition/unshield_value_balance_below_amount_error.rs deleted file mode 100644 index bbaae67bfb6..00000000000 --- a/packages/rs-dpp/src/errors/consensus/basic/state_transition/unshield_value_balance_below_amount_error.rs +++ /dev/null @@ -1,44 +0,0 @@ -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("value_balance ({value_balance}) must be >= amount ({amount})")] -#[platform_serialize(unversioned)] -pub struct UnshieldValueBalanceBelowAmountError { - /* - - DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION - - */ - value_balance: i64, - amount: u64, -} - -impl UnshieldValueBalanceBelowAmountError { - pub fn new(value_balance: i64, amount: u64) -> Self { - Self { - value_balance, - amount, - } - } - - pub fn value_balance(&self) -> i64 { - self.value_balance - } - - pub fn amount(&self) -> u64 { - self.amount - } -} - -impl From for ConsensusError { - fn from(err: UnshieldValueBalanceBelowAmountError) -> Self { - Self::BasicError(BasicError::UnshieldValueBalanceBelowAmountError(err)) - } -} diff --git a/packages/rs-dpp/src/errors/consensus/codes.rs b/packages/rs-dpp/src/errors/consensus/codes.rs index dff4f34aaa0..ed24cbc15aa 100644 --- a/packages/rs-dpp/src/errors/consensus/codes.rs +++ b/packages/rs-dpp/src/errors/consensus/codes.rs @@ -237,8 +237,6 @@ impl ErrorWithCode for BasicError { Self::ShieldedEmptyProofError(_) => 10820, Self::ShieldedZeroAnchorError(_) => 10821, Self::ShieldedInvalidValueBalanceError(_) => 10822, - Self::UnshieldAmountZeroError(_) => 10823, - Self::UnshieldValueBalanceBelowAmountError(_) => 10824, Self::ShieldedTooManyActionsError(_) => 10825, } } diff --git a/packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs b/packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs index afaaffb7ff9..cf0decf095f 100644 --- a/packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs +++ b/packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs @@ -80,9 +80,8 @@ pub fn build_shielded_withdrawal_transition( let change_amount = total_spent - required; - // ShieldedWithdrawal extra_data = output_script.as_bytes() || amount.to_le_bytes() - let mut extra_sighash_data = output_script.as_bytes().to_vec(); - extra_sighash_data.extend_from_slice(&withdrawal_amount.to_le_bytes()); + // ShieldedWithdrawal extra_data = output_script.as_bytes() + let extra_sighash_data = output_script.as_bytes().to_vec(); let bundle = build_spend_bundle( spends, @@ -99,7 +98,6 @@ pub fn build_shielded_withdrawal_transition( let sb = serialize_authorized_bundle(&bundle); ShieldedWithdrawalTransition::try_from_bundle( - withdrawal_amount, sb.actions, sb.value_balance, sb.anchor, diff --git a/packages/rs-dpp/src/shielded/builder/unshield.rs b/packages/rs-dpp/src/shielded/builder/unshield.rs index 2cac1215a53..1880bb3bbd5 100644 --- a/packages/rs-dpp/src/shielded/builder/unshield.rs +++ b/packages/rs-dpp/src/shielded/builder/unshield.rs @@ -72,9 +72,8 @@ pub fn build_unshield_transition( let change_amount = total_spent - required; - // Unshield extra_data = output_address.to_bytes() || amount.to_le_bytes() - let mut extra_sighash_data = output_address.to_bytes(); - extra_sighash_data.extend_from_slice(&unshield_amount.to_le_bytes()); + // Unshield extra_data = output_address.to_bytes() + let extra_sighash_data = output_address.to_bytes(); let bundle = build_spend_bundle( spends, @@ -92,7 +91,6 @@ pub fn build_unshield_transition( UnshieldTransition::try_from_bundle( output_address, - unshield_amount, sb.actions, sb.value_balance, sb.anchor, diff --git a/packages/rs-dpp/src/shielded/mod.rs b/packages/rs-dpp/src/shielded/mod.rs index c16f6bfd70c..2e4454c1821 100644 --- a/packages/rs-dpp/src/shielded/mod.rs +++ b/packages/rs-dpp/src/shielded/mod.rs @@ -23,9 +23,10 @@ const SIGHASH_DOMAIN: &[u8] = b"DashPlatformSighash"; /// The sighash is computed as: /// `SHA-256(SIGHASH_DOMAIN || bundle_commitment || extra_data)` /// -/// This binds transparent state transition fields (like `output_address` and `amount` -/// in unshield transitions) to the Orchard signatures, preventing replay attacks -/// where an attacker substitutes transparent fields while reusing a valid Orchard bundle. +/// This binds transparent state transition fields (like `output_address` in unshield +/// or `output_script` in shielded withdrawal) to the Orchard signatures, preventing +/// replay attacks where an attacker substitutes transparent fields while reusing a +/// valid Orchard bundle. /// /// The same computation must be used on both the signing (client) and verification /// (platform) sides. For transitions without transparent fields (shield and diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/mod.rs index 818fb952389..e62d57696a4 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/mod.rs @@ -22,7 +22,6 @@ use platform_version::version::PlatformVersion; impl ShieldedWithdrawalTransitionMethodsV0 for ShieldedWithdrawalTransition { #[cfg(feature = "state-transition-signing")] fn try_from_bundle( - amount: u64, actions: Vec, value_balance: i64, anchor: [u8; 32], @@ -40,7 +39,6 @@ impl ShieldedWithdrawalTransitionMethodsV0 for ShieldedWithdrawalTransition { .default_current_version { 0 => ShieldedWithdrawalTransitionV0::try_from_bundle( - amount, actions, value_balance, anchor, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/v0/mod.rs index 3c2f025cffc..e68b7c74560 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/v0/mod.rs @@ -14,7 +14,6 @@ pub trait ShieldedWithdrawalTransitionMethodsV0 { #[cfg(feature = "state-transition-signing")] #[allow(clippy::too_many_arguments)] fn try_from_bundle( - amount: u64, actions: Vec, value_balance: i64, anchor: [u8; 32], diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs index 9d71f97f0d6..fb15bf90272 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs @@ -30,8 +30,6 @@ use serde::{Deserialize, Serialize}; )] #[platform_serialize(unversioned)] pub struct ShieldedWithdrawalTransitionV0 { - /// Withdrawal amount in credits - pub amount: u64, /// Orchard actions (spends + change outputs) pub actions: Vec, /// Net value balance (amount + fee flowing out of shielded pool) @@ -74,7 +72,6 @@ mod tests { #[test] fn test_shielded_withdrawal_transition_v0_serialization_round_trip() { let transition = ShieldedWithdrawalTransitionV0 { - amount: 500u64, actions: vec![SerializedAction { nullifier: [1u8; 32], rk: [2u8; 32], 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 1b2f68f7afc..c7270c66a57 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 @@ -1,6 +1,4 @@ -use crate::consensus::basic::state_transition::{ - ShieldedInvalidValueBalanceError, UnshieldAmountZeroError, UnshieldValueBalanceBelowAmountError, -}; +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::{ @@ -25,13 +23,6 @@ impl StateTransitionStructureValidation for ShieldedWithdrawalTransitionV0 { return err; } - // Amount must be > 0 - if self.amount == 0 { - return SimpleConsensusValidationResult::new_with_error( - BasicError::UnshieldAmountZeroError(UnshieldAmountZeroError::new()).into(), - ); - } - // value_balance must be positive (credits flowing out of pool = amount + fee) if self.value_balance <= 0 { return SimpleConsensusValidationResult::new_with_error( @@ -44,16 +35,6 @@ impl StateTransitionStructureValidation for ShieldedWithdrawalTransitionV0 { ); } - // value_balance must be >= amount (value_balance = amount + fee) - if (self.value_balance as u64) < self.amount { - return SimpleConsensusValidationResult::new_with_error( - BasicError::UnshieldValueBalanceBelowAmountError( - UnshieldValueBalanceBelowAmountError::new(self.value_balance, self.amount), - ) - .into(), - ); - } - // Proof must not be empty if let Some(err) = validate_proof_not_empty(&self.proof) { return err; diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/v0_methods.rs index b58e01b3fa6..a27cb11c1d9 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/v0_methods.rs @@ -14,7 +14,6 @@ use platform_version::version::PlatformVersion; impl ShieldedWithdrawalTransitionMethodsV0 for ShieldedWithdrawalTransitionV0 { #[cfg(feature = "state-transition-signing")] fn try_from_bundle( - amount: u64, actions: Vec, value_balance: i64, anchor: [u8; 32], @@ -26,7 +25,6 @@ impl ShieldedWithdrawalTransitionMethodsV0 for ShieldedWithdrawalTransitionV0 { _platform_version: &PlatformVersion, ) -> Result { let transition = ShieldedWithdrawalTransitionV0 { - amount, actions, value_balance, anchor, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/mod.rs index 11c029578d4..0454e8ef3d2 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/mod.rs @@ -19,7 +19,6 @@ impl UnshieldTransitionMethodsV0 for UnshieldTransition { #[cfg(feature = "state-transition-signing")] fn try_from_bundle( output_address: PlatformAddress, - amount: u64, actions: Vec, value_balance: i64, anchor: [u8; 32], @@ -35,7 +34,6 @@ impl UnshieldTransitionMethodsV0 for UnshieldTransition { { 0 => UnshieldTransitionV0::try_from_bundle( output_address, - amount, actions, value_balance, anchor, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/v0/mod.rs index 371e3dc8c1a..7d2560ed250 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/v0/mod.rs @@ -13,7 +13,6 @@ pub trait UnshieldTransitionMethodsV0 { #[allow(clippy::too_many_arguments)] fn try_from_bundle( output_address: PlatformAddress, - amount: u64, actions: Vec, value_balance: i64, anchor: [u8; 32], diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs index 8ed98602ea6..801eccdabf0 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs @@ -31,8 +31,6 @@ use serde::{Deserialize, Serialize}; pub struct UnshieldTransitionV0 { /// Address receiving the unshielded funds pub output_address: PlatformAddress, - /// Amount being unshielded (in credits) - pub amount: u64, /// Orchard actions (spend-output pairs) pub actions: Vec, /// Net value balance (amount + fee flowing out of shielded pool) @@ -73,7 +71,6 @@ mod tests { output_address: PlatformAddress::P2pkh([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ]), - amount: 500u64, actions: vec![SerializedAction { nullifier: [1u8; 32], rk: [2u8; 32], 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 f59ccccc948..8aaec3d8a63 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,6 +1,4 @@ -use crate::consensus::basic::state_transition::{ - ShieldedInvalidValueBalanceError, UnshieldAmountZeroError, UnshieldValueBalanceBelowAmountError, -}; +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, @@ -25,13 +23,6 @@ impl StateTransitionStructureValidation for UnshieldTransitionV0 { return err; } - // Amount must be > 0 - if self.amount == 0 { - return SimpleConsensusValidationResult::new_with_error( - BasicError::UnshieldAmountZeroError(UnshieldAmountZeroError::new()).into(), - ); - } - // value_balance must be positive (credits flowing out of pool = amount + fee) if self.value_balance <= 0 { return SimpleConsensusValidationResult::new_with_error( @@ -44,16 +35,6 @@ impl StateTransitionStructureValidation for UnshieldTransitionV0 { ); } - // value_balance must be >= amount (value_balance = amount + fee) - if (self.value_balance as u64) < self.amount { - return SimpleConsensusValidationResult::new_with_error( - BasicError::UnshieldValueBalanceBelowAmountError( - UnshieldValueBalanceBelowAmountError::new(self.value_balance, self.amount), - ) - .into(), - ); - } - // Proof must not be empty if let Some(err) = validate_proof_not_empty(&self.proof) { return err; diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/v0_methods.rs index ce0d2c67e41..12c258955d2 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/v0_methods.rs @@ -13,7 +13,6 @@ impl UnshieldTransitionMethodsV0 for UnshieldTransitionV0 { #[cfg(feature = "state-transition-signing")] fn try_from_bundle( output_address: PlatformAddress, - amount: u64, actions: Vec, value_balance: i64, anchor: [u8; 32], @@ -23,7 +22,6 @@ impl UnshieldTransitionMethodsV0 for UnshieldTransitionV0 { ) -> Result { let transition = UnshieldTransitionV0 { output_address, - amount, actions, value_balance, anchor, diff --git a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs index f88f9766b50..d32586c219f 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, UnshieldAmountZeroError, UnshieldValueBalanceBelowAmountError}; +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::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,12 +956,6 @@ fn from_basic_error(basic_error: &BasicError) -> JsValue { BasicError::ShieldedInvalidValueBalanceError(e) => { generic_consensus_error!(ShieldedInvalidValueBalanceError, e).into() } - BasicError::UnshieldAmountZeroError(e) => { - generic_consensus_error!(UnshieldAmountZeroError, e).into() - } - BasicError::UnshieldValueBalanceBelowAmountError(e) => { - generic_consensus_error!(UnshieldValueBalanceBelowAmountError, e).into() - } } } From 218981f0677dc3a77bd6226208f49f5d85ba9274 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 5 Mar 2026 17:41:00 +0700 Subject: [PATCH 19/30] refactor(dpp): rename value_balance to unshielding_amount (u64) in Unshield and ShieldedWithdrawal Rename `value_balance: i64` to `unshielding_amount: u64` since these transitions always have positive outflow. Simplifies validation (== 0 instead of <= 0) and makes the type match semantics. Also adds TODO comments on `calculate_min_required_fee` returning 0 for shielded types, and replaces todo!() panics in path_elements display with simple strings. Co-Authored-By: Claude Opus 4.6 --- .../rs-dpp/src/shielded/builder/shielded_withdrawal.rs | 2 +- packages/rs-dpp/src/shielded/builder/unshield.rs | 2 +- .../state_transition_estimated_fee_validation.rs | 6 ++++-- .../shielded_withdrawal_transition/methods/mod.rs | 4 ++-- .../shielded_withdrawal_transition/methods/v0/mod.rs | 2 +- .../state_transition_estimated_fee_validation.rs | 6 ++++-- .../shielded/shielded_withdrawal_transition/v0/mod.rs | 6 +++--- .../v0/state_transition_validation.rs | 6 +++--- .../shielded_withdrawal_transition/v0/v0_methods.rs | 4 ++-- .../shielded/unshield_transition/methods/mod.rs | 4 ++-- .../shielded/unshield_transition/methods/v0/mod.rs | 2 +- .../state_transition_estimated_fee_validation.rs | 6 ++++-- .../shielded/unshield_transition/v0/mod.rs | 6 +++--- .../unshield_transition/v0/state_transition_validation.rs | 6 +++--- .../shielded/unshield_transition/v0/v0_methods.rs | 4 ++-- packages/rs-sdk-ffi/src/system/queries/path_elements.rs | 8 ++++---- 16 files changed, 40 insertions(+), 34 deletions(-) diff --git a/packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs b/packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs index cf0decf095f..2c798617385 100644 --- a/packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs +++ b/packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs @@ -99,7 +99,7 @@ pub fn build_shielded_withdrawal_transition( ShieldedWithdrawalTransition::try_from_bundle( sb.actions, - sb.value_balance, + sb.value_balance as u64, sb.anchor, sb.proof, sb.binding_signature, diff --git a/packages/rs-dpp/src/shielded/builder/unshield.rs b/packages/rs-dpp/src/shielded/builder/unshield.rs index 1880bb3bbd5..77638f1f0db 100644 --- a/packages/rs-dpp/src/shielded/builder/unshield.rs +++ b/packages/rs-dpp/src/shielded/builder/unshield.rs @@ -92,7 +92,7 @@ pub fn build_unshield_transition( UnshieldTransition::try_from_bundle( output_address, sb.actions, - sb.value_balance, + sb.value_balance as u64, sb.anchor, sb.proof, sb.binding_signature, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/state_transition_estimated_fee_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/state_transition_estimated_fee_validation.rs index d956aa98757..d706caa4327 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/state_transition_estimated_fee_validation.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/state_transition_estimated_fee_validation.rs @@ -9,8 +9,10 @@ impl StateTransitionEstimatedFeeValidation for ShieldedTransferTransition { &self, _platform_version: &PlatformVersion, ) -> Result { - // Fee for shielded transfers is paid from value balance in the orchard bundle - // Minimum fee is 0 as the actual fee is extracted from the bundle during validation + // TODO: revisit when drive/drive-abci integration lands — the shielded fee is + // embedded in the bundle's value_balance and validated on-chain via + // `validate_minimum_shielded_fee`, but this client-side estimate currently + // returns 0 which means mempool pre-checks won't reject under-fee'd txns. Ok(0) } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/mod.rs index e62d57696a4..579ed9db47a 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/mod.rs @@ -23,7 +23,7 @@ impl ShieldedWithdrawalTransitionMethodsV0 for ShieldedWithdrawalTransition { #[cfg(feature = "state-transition-signing")] fn try_from_bundle( actions: Vec, - value_balance: i64, + unshielding_amount: u64, anchor: [u8; 32], proof: Vec, binding_signature: [u8; 64], @@ -40,7 +40,7 @@ impl ShieldedWithdrawalTransitionMethodsV0 for ShieldedWithdrawalTransition { { 0 => ShieldedWithdrawalTransitionV0::try_from_bundle( actions, - value_balance, + unshielding_amount, anchor, proof, binding_signature, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/v0/mod.rs index e68b7c74560..4824b45f783 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/v0/mod.rs @@ -15,7 +15,7 @@ pub trait ShieldedWithdrawalTransitionMethodsV0 { #[allow(clippy::too_many_arguments)] fn try_from_bundle( actions: Vec, - value_balance: i64, + unshielding_amount: u64, anchor: [u8; 32], proof: Vec, binding_signature: [u8; 64], diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/state_transition_estimated_fee_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/state_transition_estimated_fee_validation.rs index 43a78d0d4a3..d41a8a3c130 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/state_transition_estimated_fee_validation.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/state_transition_estimated_fee_validation.rs @@ -9,8 +9,10 @@ impl StateTransitionEstimatedFeeValidation for ShieldedWithdrawalTransition { &self, _platform_version: &PlatformVersion, ) -> Result { - // Fee for shielded withdrawal is paid from value balance in the orchard bundle - // Minimum fee is 0 as the actual fee is extracted from the bundle during validation + // TODO: revisit when drive/drive-abci integration lands — the shielded fee is + // embedded in unshielding_amount and validated on-chain via + // `validate_minimum_shielded_fee`, but this client-side estimate currently + // returns 0 which means mempool pre-checks won't reject under-fee'd txns. Ok(0) } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs index fb15bf90272..0f436aa744a 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs @@ -32,8 +32,8 @@ use serde::{Deserialize, Serialize}; pub struct ShieldedWithdrawalTransitionV0 { /// Orchard actions (spends + change outputs) pub actions: Vec, - /// Net value balance (amount + fee flowing out of shielded pool) - pub value_balance: i64, + /// Total credits leaving the shielded pool (recipient amount + fee) + pub unshielding_amount: u64, /// Sinsemilla root of the note commitment tree (Orchard Anchor) pub anchor: [u8; 32], /// Halo2 proof bytes @@ -80,7 +80,7 @@ mod tests { cv_net: [5u8; 32], spend_auth_sig: [6u8; 64], }], - value_balance: 1000i64, + unshielding_amount: 1000u64, anchor: [7u8; 32], proof: vec![8u8; 100], binding_signature: [9u8; 64], 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 c7270c66a57..b5e3429bbf2 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 @@ -23,12 +23,12 @@ impl StateTransitionStructureValidation for ShieldedWithdrawalTransitionV0 { return err; } - // value_balance must be positive (credits flowing out of pool = amount + fee) - if self.value_balance <= 0 { + // unshielding_amount must be positive (credits flowing out of pool) + if self.unshielding_amount == 0 { return SimpleConsensusValidationResult::new_with_error( BasicError::ShieldedInvalidValueBalanceError( ShieldedInvalidValueBalanceError::new( - "shielded withdrawal value_balance must be positive".to_string(), + "shielded withdrawal unshielding_amount must be positive".to_string(), ), ) .into(), diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/v0_methods.rs index a27cb11c1d9..31fe6a01bbf 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/v0_methods.rs @@ -15,7 +15,7 @@ impl ShieldedWithdrawalTransitionMethodsV0 for ShieldedWithdrawalTransitionV0 { #[cfg(feature = "state-transition-signing")] fn try_from_bundle( actions: Vec, - value_balance: i64, + unshielding_amount: u64, anchor: [u8; 32], proof: Vec, binding_signature: [u8; 64], @@ -26,7 +26,7 @@ impl ShieldedWithdrawalTransitionMethodsV0 for ShieldedWithdrawalTransitionV0 { ) -> Result { let transition = ShieldedWithdrawalTransitionV0 { actions, - value_balance, + unshielding_amount, anchor, proof, binding_signature, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/mod.rs index 0454e8ef3d2..45e189e6d1c 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/mod.rs @@ -20,7 +20,7 @@ impl UnshieldTransitionMethodsV0 for UnshieldTransition { fn try_from_bundle( output_address: PlatformAddress, actions: Vec, - value_balance: i64, + unshielding_amount: u64, anchor: [u8; 32], proof: Vec, binding_signature: [u8; 64], @@ -35,7 +35,7 @@ impl UnshieldTransitionMethodsV0 for UnshieldTransition { 0 => UnshieldTransitionV0::try_from_bundle( output_address, actions, - value_balance, + unshielding_amount, anchor, proof, binding_signature, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/v0/mod.rs index 7d2560ed250..d68e385aaf2 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/v0/mod.rs @@ -14,7 +14,7 @@ pub trait UnshieldTransitionMethodsV0 { fn try_from_bundle( output_address: PlatformAddress, actions: Vec, - value_balance: i64, + unshielding_amount: u64, anchor: [u8; 32], proof: Vec, binding_signature: [u8; 64], diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/state_transition_estimated_fee_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/state_transition_estimated_fee_validation.rs index 4f1d94063e7..93559c7e7a5 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/state_transition_estimated_fee_validation.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/state_transition_estimated_fee_validation.rs @@ -9,8 +9,10 @@ impl StateTransitionEstimatedFeeValidation for UnshieldTransition { &self, _platform_version: &PlatformVersion, ) -> Result { - // Fee for unshield is paid from value balance in the orchard bundle - // Minimum fee is 0 as the actual fee is extracted from the bundle during validation + // TODO: revisit when drive/drive-abci integration lands — the shielded fee is + // embedded in unshielding_amount and validated on-chain via + // `validate_minimum_shielded_fee`, but this client-side estimate currently + // returns 0 which means mempool pre-checks won't reject under-fee'd txns. Ok(0) } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs index 801eccdabf0..4800b7c0562 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs @@ -33,8 +33,8 @@ pub struct UnshieldTransitionV0 { pub output_address: PlatformAddress, /// Orchard actions (spend-output pairs) pub actions: Vec, - /// Net value balance (amount + fee flowing out of shielded pool) - pub value_balance: i64, + /// Total credits leaving the shielded pool (recipient amount + fee) + pub unshielding_amount: u64, /// Sinsemilla root of the note commitment tree (Orchard Anchor) pub anchor: [u8; 32], /// Halo2 proof bytes @@ -79,7 +79,7 @@ mod tests { cv_net: [5u8; 32], spend_auth_sig: [6u8; 64], }], - value_balance: 1000i64, + unshielding_amount: 1000u64, anchor: [7u8; 32], proof: vec![8u8; 100], binding_signature: [9u8; 64], 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 8aaec3d8a63..446de8ad374 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 @@ -23,12 +23,12 @@ impl StateTransitionStructureValidation for UnshieldTransitionV0 { return err; } - // value_balance must be positive (credits flowing out of pool = amount + fee) - if self.value_balance <= 0 { + // unshielding_amount must be positive (credits flowing out of pool) + if self.unshielding_amount == 0 { return SimpleConsensusValidationResult::new_with_error( BasicError::ShieldedInvalidValueBalanceError( ShieldedInvalidValueBalanceError::new( - "unshield value_balance must be positive".to_string(), + "unshield unshielding_amount must be positive".to_string(), ), ) .into(), diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/v0_methods.rs index 12c258955d2..8c8701e47c9 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/v0_methods.rs @@ -14,7 +14,7 @@ impl UnshieldTransitionMethodsV0 for UnshieldTransitionV0 { fn try_from_bundle( output_address: PlatformAddress, actions: Vec, - value_balance: i64, + unshielding_amount: u64, anchor: [u8; 32], proof: Vec, binding_signature: [u8; 64], @@ -23,7 +23,7 @@ impl UnshieldTransitionMethodsV0 for UnshieldTransitionV0 { let transition = UnshieldTransitionV0 { output_address, actions, - value_balance, + unshielding_amount, anchor, proof, binding_signature, diff --git a/packages/rs-sdk-ffi/src/system/queries/path_elements.rs b/packages/rs-sdk-ffi/src/system/queries/path_elements.rs index df94197cf43..432c4353d08 100644 --- a/packages/rs-sdk-ffi/src/system/queries/path_elements.rs +++ b/packages/rs-sdk-ffi/src/system/queries/path_elements.rs @@ -156,11 +156,11 @@ fn get_path_elements( Element::ProvableCountSumTree(_, count, sum, _) => { format!("provable_count_sum_tree:{}:{}", count, sum) } - Element::CommitmentTree(_, _, _) => todo!("CommitmentTree display"), - Element::MmrTree(_, _) => todo!("MmrTree display"), - Element::BulkAppendTree(_, _, _) => todo!("BulkAppendTree display"), + Element::CommitmentTree(_, _, _) => "commitment_tree".to_string(), + Element::MmrTree(_, _) => "mmr_tree".to_string(), + Element::BulkAppendTree(_, _, _) => "bulk_append_tree".to_string(), Element::DenseAppendOnlyFixedSizeTree(_, _, _) => { - todo!("DenseAppendOnlyFixedSizeTree display") + "dense_append_only_fixed_size_tree".to_string() } }; From caf604f363e0e1f11e5e6e2b00d4fd91f1a06ddc Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 5 Mar 2026 17:48:27 +0700 Subject: [PATCH 20/30] refactor(dpp): address PR review for ShieldFromAssetLock - Remove user_fee_increase field (fee is implicit from asset lock value) - Add proof to binary_property_paths alongside signature Co-Authored-By: Claude Opus 4.6 --- .../rs-dpp/src/shielded/builder/shield_from_asset_lock.rs | 5 +---- .../shielded/shield_from_asset_lock_transition/fields.rs | 2 ++ .../shield_from_asset_lock_transition/methods/mod.rs | 3 --- .../shield_from_asset_lock_transition/methods/v0/mod.rs | 3 +-- .../shielded/shield_from_asset_lock_transition/mod.rs | 4 ++-- .../shielded/shield_from_asset_lock_transition/v0/mod.rs | 6 ------ .../shielded/shield_from_asset_lock_transition/v0/types.rs | 4 ++-- .../shield_from_asset_lock_transition/v0/v0_methods.rs | 4 +--- 8 files changed, 9 insertions(+), 22 deletions(-) diff --git a/packages/rs-dpp/src/shielded/builder/shield_from_asset_lock.rs b/packages/rs-dpp/src/shielded/builder/shield_from_asset_lock.rs index 8ddfebaa04d..d91fa6d6b1c 100644 --- a/packages/rs-dpp/src/shielded/builder/shield_from_asset_lock.rs +++ b/packages/rs-dpp/src/shielded/builder/shield_from_asset_lock.rs @@ -1,5 +1,5 @@ use crate::address_funds::OrchardAddress; -use crate::prelude::{AssetLockProof, UserFeeIncrease}; +use crate::prelude::AssetLockProof; use crate::state_transition::shield_from_asset_lock_transition::methods::ShieldFromAssetLockTransitionMethodsV0; use crate::state_transition::shield_from_asset_lock_transition::ShieldFromAssetLockTransition; use crate::state_transition::StateTransition; @@ -18,7 +18,6 @@ use super::{build_output_only_bundle, serialize_authorized_bundle, OrchardProver /// - `shield_amount` - Amount of credits to shield (from the asset lock) /// - `asset_lock_proof` - Proof that funds are locked on core chain /// - `asset_lock_private_key` - Private key for the asset lock (signs the transition) -/// - `user_fee_increase` - Fee multiplier (0 = 100% base fee) /// - `prover` - Orchard prover (holds the Halo 2 proving key) /// - `memo` - 36-byte structured memo for the recipient (4-byte type tag + 32-byte payload) /// - `platform_version` - Protocol version @@ -28,7 +27,6 @@ pub fn build_shield_from_asset_lock_transition( shield_amount: u64, asset_lock_proof: AssetLockProof, asset_lock_private_key: &[u8], - user_fee_increase: UserFeeIncrease, prover: &P, memo: [u8; 36], platform_version: &PlatformVersion, @@ -56,7 +54,6 @@ pub fn build_shield_from_asset_lock_transition( sb.anchor, sb.proof, sb.binding_signature, - user_fee_increase, platform_version, ) } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/fields.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/fields.rs index ef367006ce7..4b00879060f 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/fields.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/fields.rs @@ -1 +1,3 @@ pub use crate::state_transition::state_transitions::common_fields::property_names::SIGNATURE; + +pub const PROOF: &str = "proof"; diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/mod.rs index 4192559df0c..a71e402d4ca 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/mod.rs @@ -9,7 +9,6 @@ use crate::shielded::SerializedAction; use crate::state_transition::shield_from_asset_lock_transition::ShieldFromAssetLockTransition; #[cfg(feature = "state-transition-signing")] use crate::{ - prelude::UserFeeIncrease, state_transition::{ shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0, StateTransition, }, @@ -28,7 +27,6 @@ impl ShieldFromAssetLockTransitionMethodsV0 for ShieldFromAssetLockTransition { anchor: [u8; 32], proof: Vec, binding_signature: [u8; 64], - user_fee_increase: UserFeeIncrease, platform_version: &PlatformVersion, ) -> Result { match platform_version @@ -45,7 +43,6 @@ impl ShieldFromAssetLockTransitionMethodsV0 for ShieldFromAssetLockTransition { anchor, proof, binding_signature, - user_fee_increase, platform_version, ), version => Err(ProtocolError::UnknownVersionMismatch { diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/v0/mod.rs index 6a91d10cd31..3ac5404bda8 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/v0/mod.rs @@ -4,7 +4,7 @@ use crate::prelude::AssetLockProof; use crate::shielded::SerializedAction; use crate::state_transition::StateTransitionType; #[cfg(feature = "state-transition-signing")] -use crate::{prelude::UserFeeIncrease, state_transition::StateTransition, ProtocolError}; +use crate::{state_transition::StateTransition, ProtocolError}; #[cfg(feature = "state-transition-signing")] use platform_version::version::PlatformVersion; @@ -19,7 +19,6 @@ pub trait ShieldFromAssetLockTransitionMethodsV0 { anchor: [u8; 32], proof: Vec, binding_signature: [u8; 64], - user_fee_increase: UserFeeIncrease, platform_version: &PlatformVersion, ) -> Result; diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/mod.rs index 776eafeabfe..0ac7cbafc28 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/mod.rs @@ -7,7 +7,7 @@ mod state_transition_validation; pub mod v0; mod version; -use crate::state_transition::shield_from_asset_lock_transition::fields::SIGNATURE; +use crate::state_transition::shield_from_asset_lock_transition::fields::{PROOF, SIGNATURE}; use crate::state_transition::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0; use crate::state_transition::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0Signable; use crate::state_transition::StateTransitionFieldTypes; @@ -58,6 +58,6 @@ impl StateTransitionFieldTypes for ShieldFromAssetLockTransition { } fn binary_property_paths() -> Vec<&'static str> { - vec![SIGNATURE] + vec![SIGNATURE, PROOF] } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs index fc352d4a054..7492a91551c 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs @@ -6,7 +6,6 @@ pub(super) mod v0_methods; mod version; use crate::identity::state_transition::asset_lock_proof::AssetLockProof; -use crate::prelude::UserFeeIncrease; use crate::shielded::SerializedAction; use crate::ProtocolError; use bincode::{Decode, Encode}; @@ -49,10 +48,6 @@ pub struct ShieldFromAssetLockTransitionV0 { serde(with = "crate::shielded::serde_bytes_64") )] pub binding_signature: [u8; 64], - // TODO: remove user_fee_increase — the fee is implicitly the difference between - // the asset lock value and value_balance, so no separate fee multiplier is needed. - /// Fee multiplier - pub user_fee_increase: UserFeeIncrease, /// ECDSA signature over the signable bytes (excluded from sig hash) #[platform_signable(exclude_from_sig_hash)] pub signature: BinaryData, @@ -98,7 +93,6 @@ mod tests { anchor: [7u8; 32], proof: vec![8u8; 100], binding_signature: [9u8; 64], - user_fee_increase: 0u16, signature: BinaryData::new(vec![10u8; 65]), }; diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/types.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/types.rs index 37cccd61813..a65cb0fde42 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/types.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/types.rs @@ -1,4 +1,4 @@ -use crate::state_transition::shield_from_asset_lock_transition::fields::SIGNATURE; +use crate::state_transition::shield_from_asset_lock_transition::fields::{PROOF, SIGNATURE}; use crate::state_transition::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0; use crate::state_transition::StateTransitionFieldTypes; @@ -12,6 +12,6 @@ impl StateTransitionFieldTypes for ShieldFromAssetLockTransitionV0 { } fn binary_property_paths() -> Vec<&'static str> { - vec![SIGNATURE] + vec![SIGNATURE, PROOF] } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/v0_methods.rs index 9df7f00b518..c10d1907a9b 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/v0_methods.rs @@ -7,7 +7,7 @@ use crate::shielded::SerializedAction; use crate::state_transition::shield_from_asset_lock_transition::methods::ShieldFromAssetLockTransitionMethodsV0; use crate::state_transition::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0; #[cfg(feature = "state-transition-signing")] -use crate::{prelude::UserFeeIncrease, state_transition::StateTransition, ProtocolError}; +use crate::{state_transition::StateTransition, ProtocolError}; #[cfg(feature = "state-transition-signing")] use dashcore::signer; #[cfg(feature = "state-transition-signing")] @@ -23,7 +23,6 @@ impl ShieldFromAssetLockTransitionMethodsV0 for ShieldFromAssetLockTransitionV0 anchor: [u8; 32], proof: Vec, binding_signature: [u8; 64], - user_fee_increase: UserFeeIncrease, _platform_version: &PlatformVersion, ) -> Result { // Create the unsigned transition @@ -34,7 +33,6 @@ impl ShieldFromAssetLockTransitionMethodsV0 for ShieldFromAssetLockTransitionV0 anchor, proof, binding_signature, - user_fee_increase, signature: Default::default(), }; From d5940a66c63010f06fe5ed2f83e0236d1d80d2ba Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 5 Mar 2026 17:57:19 +0700 Subject: [PATCH 21/30] fix(dpp): validate withdrawal/unshield amount is within i64::MAX Orchard value balances are signed i64, so amounts must not exceed i64::MAX when represented as u64. Co-Authored-By: Claude Opus 4.6 --- .../rs-dpp/src/shielded/builder/shielded_withdrawal.rs | 8 ++++++++ packages/rs-dpp/src/shielded/builder/unshield.rs | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs b/packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs index 2c798617385..750b7428e03 100644 --- a/packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs +++ b/packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs @@ -50,6 +50,14 @@ pub fn build_shielded_withdrawal_transition( fee: Option, platform_version: &PlatformVersion, ) -> Result { + if withdrawal_amount > i64::MAX as u64 { + return Err(ProtocolError::Generic(format!( + "withdrawal amount {} exceeds maximum allowed value {}", + withdrawal_amount, + i64::MAX as u64 + ))); + } + let total_spent: u64 = spends.iter().map(|s| s.note.value().inner()).sum(); // Conservative action count: at least (spends, 1) since we have a change output. diff --git a/packages/rs-dpp/src/shielded/builder/unshield.rs b/packages/rs-dpp/src/shielded/builder/unshield.rs index 77638f1f0db..d7589ad1720 100644 --- a/packages/rs-dpp/src/shielded/builder/unshield.rs +++ b/packages/rs-dpp/src/shielded/builder/unshield.rs @@ -44,6 +44,14 @@ pub fn build_unshield_transition( fee: Option, platform_version: &PlatformVersion, ) -> Result { + if unshield_amount > i64::MAX as u64 { + return Err(ProtocolError::Generic(format!( + "unshield amount {} exceeds maximum allowed value {}", + unshield_amount, + i64::MAX as u64 + ))); + } + let total_spent: u64 = spends.iter().map(|s| s.note.value().inner()).sum(); // Conservative action count: at least (spends, 1) since we have a change output. From 5950a33c4718df442838d3967843d20aee4a9a99 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 5 Mar 2026 17:58:04 +0700 Subject: [PATCH 22/30] fix(dpp): validate unshielding_amount <= i64::MAX in structure validation Co-Authored-By: Claude Opus 4.6 --- .../v0/state_transition_validation.rs | 14 +++++++++++++- .../v0/state_transition_validation.rs | 13 ++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) 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 b5e3429bbf2..899c332784d 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 @@ -23,7 +23,7 @@ impl StateTransitionStructureValidation for ShieldedWithdrawalTransitionV0 { return err; } - // unshielding_amount must be positive (credits flowing out of pool) + // unshielding_amount must be positive and within i64::MAX if self.unshielding_amount == 0 { return SimpleConsensusValidationResult::new_with_error( BasicError::ShieldedInvalidValueBalanceError( @@ -35,6 +35,18 @@ impl StateTransitionStructureValidation for ShieldedWithdrawalTransitionV0 { ); } + if self.unshielding_amount > i64::MAX as u64 { + return SimpleConsensusValidationResult::new_with_error( + BasicError::ShieldedInvalidValueBalanceError( + ShieldedInvalidValueBalanceError::new( + "shielded withdrawal unshielding_amount exceeds maximum allowed value" + .to_string(), + ), + ) + .into(), + ); + } + // Proof must not be empty if let Some(err) = validate_proof_not_empty(&self.proof) { return err; 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 446de8ad374..9b26a0f760f 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 @@ -23,7 +23,7 @@ impl StateTransitionStructureValidation for UnshieldTransitionV0 { return err; } - // unshielding_amount must be positive (credits flowing out of pool) + // unshielding_amount must be positive and within i64::MAX if self.unshielding_amount == 0 { return SimpleConsensusValidationResult::new_with_error( BasicError::ShieldedInvalidValueBalanceError( @@ -35,6 +35,17 @@ impl StateTransitionStructureValidation for UnshieldTransitionV0 { ); } + if self.unshielding_amount > i64::MAX as u64 { + return SimpleConsensusValidationResult::new_with_error( + BasicError::ShieldedInvalidValueBalanceError( + ShieldedInvalidValueBalanceError::new( + "unshield unshielding_amount exceeds maximum allowed value".to_string(), + ), + ) + .into(), + ); + } + // Proof must not be empty if let Some(err) = validate_proof_not_empty(&self.proof) { return err; From 1ad16f863585e40042567ade6a41015390b81501 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 5 Mar 2026 18:02:25 +0700 Subject: [PATCH 23/30] chore(dpp): cargo fmt Co-Authored-By: Claude Opus 4.6 --- packages/rs-dpp/src/errors/consensus/basic/basic_error.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 5407735af79..05dee2b8ab2 100644 --- a/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs +++ b/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs @@ -81,8 +81,7 @@ use crate::consensus::basic::state_transition::{ ShieldedNoActionsError, ShieldedTooManyActionsError, ShieldedZeroAnchorError, StateTransitionMaxSizeExceededError, StateTransitionNotActiveError, TransitionNoInputsError, TransitionNoOutputsError, TransitionOverMaxInputsError, TransitionOverMaxOutputsError, - WithdrawalBalanceMismatchError, - WithdrawalBelowMinAmountError, + WithdrawalBalanceMismatchError, WithdrawalBelowMinAmountError, }; use crate::consensus::basic::{ IncompatibleProtocolVersionError, UnsupportedFeatureError, UnsupportedProtocolVersionError, @@ -674,7 +673,6 @@ pub enum BasicError { #[error(transparent)] ShieldedInvalidValueBalanceError(ShieldedInvalidValueBalanceError), - } impl From for ConsensusError { From cf113908d1f42244a685a9febb05ba4de04a7b5b Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 5 Mar 2026 19:36:57 +0700 Subject: [PATCH 24/30] fix(drive): update PathQuery Display assertion to match grovedb changes The upstream grovedb Display impl now includes `0x` prefixes on hex bytes and prints the `add_parent_tree_on_subquery` field. Co-Authored-By: Claude Opus 4.6 --- packages/rs-drive/src/query/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rs-drive/src/query/mod.rs b/packages/rs-drive/src/query/mod.rs index ef57daaa180..f3e29653687 100644 --- a/packages/rs-drive/src/query/mod.rs +++ b/packages/rs-drive/src/query/mod.rs @@ -2781,7 +2781,7 @@ mod tests { .construct_path_query(None, platform_version) .expect("expected to create path query"); - assert_eq!(path_query.to_string(), "PathQuery { path: [@, 0x1da29f488023e306ff9a680bc9837153fb0778c8ee9c934a87dc0de1d69abd3c, 0x01, domain, 0x7265636f7264732e6964656e74697479], query: SizedQuery { query: Query {\n items: [\n RangeTo(.. 8dc201fd7ad7905f8a84d66218e2b387daea7fe4739ae0e21e8c3ee755e6a2c0),\n ],\n default_subquery_branch: SubqueryBranch { subquery_path: [00], subquery: Query {\n items: [\n RangeFull,\n ],\n default_subquery_branch: SubqueryBranch { subquery_path: None subquery: None },\n left_to_right: false,\n} },\n conditional_subquery_branches: {\n Key(): SubqueryBranch { subquery_path: [00], subquery: Query {\n items: [\n RangeFull,\n ],\n default_subquery_branch: SubqueryBranch { subquery_path: None subquery: None },\n left_to_right: false,\n} },\n },\n left_to_right: false,\n}, limit: 6 } }"); + assert_eq!(path_query.to_string(), "PathQuery { path: [@, 0x1da29f488023e306ff9a680bc9837153fb0778c8ee9c934a87dc0de1d69abd3c, 0x01, domain, 0x7265636f7264732e6964656e74697479], query: SizedQuery { query: Query {\n items: [\n RangeTo(.. 0x8dc201fd7ad7905f8a84d66218e2b387daea7fe4739ae0e21e8c3ee755e6a2c0),\n ],\n default_subquery_branch: SubqueryBranch { subquery_path: [0x00], subquery: Query {\n items: [\n RangeFull,\n ],\n default_subquery_branch: SubqueryBranch { subquery_path: None subquery: None },\n left_to_right: false,\n add_parent_tree_on_subquery: false,\n} },\n conditional_subquery_branches: {\n Key(): SubqueryBranch { subquery_path: [0x00], subquery: Query {\n items: [\n RangeFull,\n ],\n default_subquery_branch: SubqueryBranch { subquery_path: None subquery: None },\n left_to_right: false,\n add_parent_tree_on_subquery: false,\n} },\n },\n left_to_right: false,\n add_parent_tree_on_subquery: false,\n}, limit: 6 } }"); // Serialize the PathQuery to a Vec let encoded = bincode::encode_to_vec(&path_query, bincode::config::standard()) From 33d5ac4ec86fc6be81342142c64e02ba996aa748 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 5 Mar 2026 19:46:15 +0700 Subject: [PATCH 25/30] refactor(dpp): move serde_bytes_64 to serialization module and extract OrchardAddress - Move serde_bytes_64 from shielded/mod.rs to serialization/serde_bytes_64.rs with hex encoding for human-readable formats (JSON) - Extract OrchardAddress into its own address_funds/orchard_address.rs module, gated once with #[cfg(feature = "shielded-tx")] at the module level Co-Authored-By: Claude Opus 4.6 --- packages/rs-dpp/src/address_funds/mod.rs | 4 + .../src/address_funds/orchard_address.rs | 286 +++++++++++++++++ .../src/address_funds/platform_address.rs | 301 ------------------ packages/rs-dpp/src/serialization/mod.rs | 2 + .../src/serialization/serde_bytes_64.rs | 33 ++ packages/rs-dpp/src/shielded/mod.rs | 19 +- .../v0/mod.rs | 2 +- .../shielded/shield_transition/v0/mod.rs | 2 +- .../shielded_transfer_transition/v0/mod.rs | 2 +- .../shielded_withdrawal_transition/v0/mod.rs | 2 +- .../shielded/unshield_transition/v0/mod.rs | 2 +- 11 files changed, 331 insertions(+), 324 deletions(-) create mode 100644 packages/rs-dpp/src/address_funds/orchard_address.rs create mode 100644 packages/rs-dpp/src/serialization/serde_bytes_64.rs diff --git a/packages/rs-dpp/src/address_funds/mod.rs b/packages/rs-dpp/src/address_funds/mod.rs index 68508382934..15f2e71de4c 100644 --- a/packages/rs-dpp/src/address_funds/mod.rs +++ b/packages/rs-dpp/src/address_funds/mod.rs @@ -1,9 +1,13 @@ pub mod fee_strategy; +#[cfg(feature = "shielded-tx")] +mod orchard_address; mod platform_address; mod witness; mod witness_verification_operations; pub use fee_strategy::*; +#[cfg(feature = "shielded-tx")] +pub use orchard_address::*; pub use platform_address::*; pub use witness::*; pub use witness_verification_operations::*; diff --git a/packages/rs-dpp/src/address_funds/orchard_address.rs b/packages/rs-dpp/src/address_funds/orchard_address.rs new file mode 100644 index 00000000000..02173a0134a --- /dev/null +++ b/packages/rs-dpp/src/address_funds/orchard_address.rs @@ -0,0 +1,286 @@ +use bech32::{Bech32m, Hrp}; +use dashcore::Network; + +use crate::address_funds::platform_address::{PLATFORM_HRP_MAINNET, PLATFORM_HRP_TESTNET}; +use crate::address_funds::PlatformAddress; +use crate::ProtocolError; + +/// Size of the Orchard diversifier (11 bytes). +pub const ORCHARD_DIVERSIFIER_SIZE: usize = 11; +/// Size of the Orchard diversified transmission key pk_d (32 bytes, Pallas curve point). +pub const ORCHARD_PKD_SIZE: usize = 32; +/// Total size of a raw Orchard payment address (43 bytes = diversifier + pk_d). +pub const ORCHARD_ADDRESS_SIZE: usize = ORCHARD_DIVERSIFIER_SIZE + ORCHARD_PKD_SIZE; + +/// An Orchard shielded payment address. +/// +/// Composed of a diversifier (11 bytes) and a diversified transmission key (32 bytes). +/// The diversifier enables a single spending key to derive an unlimited number of +/// unlinkable payment addresses. Only the holder of the corresponding FullViewingKey +/// (or IncomingViewingKey) can link diversified addresses to the same wallet. +/// +/// Bech32m encoding uses type byte `0x10`, producing addresses that start with `z`: +/// - Mainnet: `dash1z...` +/// - Testnet: `tdash1z...` +/// +/// The raw Orchard address format matches Zcash Orchard (43 bytes), but the +/// string encoding is Dash-specific (no F4Jumble, no Unified Address wrapper). +/// +/// Wraps `grovedb_commitment_tree::PaymentAddress`. Use [`From`] +/// to convert from the orchard crate's native type, or [`inner()`](OrchardAddress::inner) +/// / [`into_inner()`](OrchardAddress::into_inner) to access the wrapped address. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct OrchardAddress(grovedb_commitment_tree::PaymentAddress); + +impl OrchardAddress { + /// Type byte for Orchard addresses in bech32m encoding (user-facing). + /// Produces 'z' as the first bech32 character. + pub const ORCHARD_TYPE: u8 = 0x10; + + /// Returns the inner [`PaymentAddress`](grovedb_commitment_tree::PaymentAddress). + pub fn inner(&self) -> &grovedb_commitment_tree::PaymentAddress { + &self.0 + } + + /// Consumes the wrapper and returns the inner `PaymentAddress`. + pub fn into_inner(self) -> grovedb_commitment_tree::PaymentAddress { + self.0 + } + + /// Creates an OrchardAddress from a 43-byte raw address. + /// + /// The first 11 bytes are the diversifier, the next 32 are pk_d. + /// Returns an error if `pk_d` is not a valid Pallas curve point. + pub fn from_raw_bytes(bytes: &[u8; ORCHARD_ADDRESS_SIZE]) -> Result { + let addr = + Option::from(grovedb_commitment_tree::PaymentAddress::from_raw_address_bytes(bytes)) + .ok_or_else(|| { + ProtocolError::DecodingError( + "OrchardAddress pk_d is not a valid Pallas curve point".to_string(), + ) + })?; + Ok(Self(addr)) + } + + /// Returns the raw 43-byte address (diversifier || pk_d). + pub fn to_raw_bytes(&self) -> [u8; ORCHARD_ADDRESS_SIZE] { + self.0.to_raw_address_bytes() + } + + /// Encodes the OrchardAddress as a bech32m string for the specified network. + /// + /// Format: `1` + /// - Data: type_byte (0x10) || diversifier (11 bytes) || pk_d (32 bytes) + /// - Total payload: 44 bytes + /// - Checksum: bech32m (BIP-350) + pub fn to_bech32m_string(&self, network: Network) -> String { + let hrp_str = PlatformAddress::hrp_for_network(network); + let hrp = Hrp::parse(hrp_str).expect("HRP is valid"); + + let raw = self.to_raw_bytes(); + let mut payload = Vec::with_capacity(1 + ORCHARD_ADDRESS_SIZE); + payload.push(Self::ORCHARD_TYPE); + payload.extend_from_slice(&raw); + + bech32::encode::(hrp, &payload).expect("encoding should succeed") + } + + /// Decodes a bech32m-encoded Orchard address string. + /// + /// # Returns + /// - `Ok((OrchardAddress, Network))` - The decoded address and its network + /// - `Err(ProtocolError)` - If the address is invalid + pub fn from_bech32m_string(s: &str) -> Result<(Self, Network), ProtocolError> { + let (hrp, data) = + bech32::decode(s).map_err(|e| ProtocolError::DecodingError(format!("{}", e)))?; + + let hrp_lower = hrp.as_str().to_ascii_lowercase(); + let network = match hrp_lower.as_str() { + s if s == PLATFORM_HRP_MAINNET => Network::Dash, + s if s == PLATFORM_HRP_TESTNET => Network::Testnet, + _ => { + return Err(ProtocolError::DecodingError(format!( + "invalid HRP '{}': expected '{}' or '{}'", + hrp, PLATFORM_HRP_MAINNET, PLATFORM_HRP_TESTNET + ))) + } + }; + + // Validate payload: 1 type byte + 11 diversifier + 32 pk_d = 44 bytes + if data.len() != 1 + ORCHARD_ADDRESS_SIZE { + return Err(ProtocolError::DecodingError(format!( + "invalid Orchard address length: expected {} bytes, got {}", + 1 + ORCHARD_ADDRESS_SIZE, + data.len() + ))); + } + + if data[0] != Self::ORCHARD_TYPE { + return Err(ProtocolError::DecodingError(format!( + "invalid Orchard address type byte: expected 0x{:02x}, got 0x{:02x}", + Self::ORCHARD_TYPE, + data[0] + ))); + } + + let mut raw = [0u8; ORCHARD_ADDRESS_SIZE]; + raw.copy_from_slice(&data[1..]); + Self::from_raw_bytes(&raw).map(|addr| (addr, network)) + } +} + +/// Infallible conversion from the orchard crate's `PaymentAddress` to `OrchardAddress`. +impl From for OrchardAddress { + fn from(addr: grovedb_commitment_tree::PaymentAddress) -> Self { + Self(addr) + } +} + +/// Infallible conversion from a reference to `PaymentAddress`. +impl From<&grovedb_commitment_tree::PaymentAddress> for OrchardAddress { + fn from(addr: &grovedb_commitment_tree::PaymentAddress) -> Self { + Self(*addr) + } +} + +impl std::fmt::Display for OrchardAddress { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let raw = self.to_raw_bytes(); + write!( + f, + "Orchard(d={}, pk_d={})", + hex::encode(&raw[..ORCHARD_DIVERSIFIER_SIZE]), + hex::encode(&raw[ORCHARD_DIVERSIFIER_SIZE..]) + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use bech32::Hrp; + + fn test_orchard_address() -> OrchardAddress { + use grovedb_commitment_tree::{FullViewingKey, Scope, SpendingKey}; + let sk = SpendingKey::from_bytes([42u8; 32]).unwrap(); + let fvk = FullViewingKey::from(&sk); + let payment_address = fvk.address_at(0u32, Scope::External); + OrchardAddress::from(payment_address) + } + + #[test] + fn test_orchard_address_raw_bytes_roundtrip() { + let address = test_orchard_address(); + let raw = address.to_raw_bytes(); + assert_eq!(raw.len(), 43); + + let recovered = OrchardAddress::from_raw_bytes(&raw).unwrap(); + assert_eq!(recovered, address); + } + + #[test] + fn test_orchard_bech32m_mainnet_roundtrip() { + let address = test_orchard_address(); + + let encoded = address.to_bech32m_string(Network::Dash); + assert!( + encoded.starts_with("dash1z"), + "Orchard mainnet address should start with 'dash1z', got: {}", + encoded + ); + + let (decoded, network) = + OrchardAddress::from_bech32m_string(&encoded).expect("decoding should succeed"); + assert_eq!(decoded, address); + assert_eq!(network, Network::Dash); + } + + #[test] + fn test_orchard_bech32m_testnet_roundtrip() { + let address = test_orchard_address(); + + let encoded = address.to_bech32m_string(Network::Testnet); + assert!( + encoded.starts_with("tdash1z"), + "Orchard testnet address should start with 'tdash1z', got: {}", + encoded + ); + + let (decoded, network) = + OrchardAddress::from_bech32m_string(&encoded).expect("decoding should succeed"); + assert_eq!(decoded, address); + assert_eq!(network, Network::Testnet); + } + + #[test] + fn test_orchard_bech32m_wrong_type_byte_fails() { + // Manually construct an address with P2PKH type byte (0xb0) but 44-byte payload + let hrp = Hrp::parse("dash").unwrap(); + let mut payload = vec![PlatformAddress::P2PKH_TYPE]; // Wrong type byte + payload.extend_from_slice(&[0u8; 43]); + let encoded = bech32::encode::(hrp, &payload).unwrap(); + + let result = OrchardAddress::from_bech32m_string(&encoded); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("invalid Orchard address type byte")); + } + + #[test] + fn test_orchard_bech32m_wrong_length_fails() { + // Too short (only 20 bytes instead of 43) + let hrp = Hrp::parse("dash").unwrap(); + let mut payload = vec![OrchardAddress::ORCHARD_TYPE]; + payload.extend_from_slice(&[0u8; 20]); + let encoded = bech32::encode::(hrp, &payload).unwrap(); + + let result = OrchardAddress::from_bech32m_string(&encoded); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("invalid Orchard address length")); + } + + #[test] + fn test_orchard_and_platform_addresses_are_distinguishable() { + let p2pkh = PlatformAddress::P2pkh([0xAB; 20]); + let p2sh = PlatformAddress::P2sh([0xAB; 20]); + let orchard = test_orchard_address(); + + let p2pkh_enc = p2pkh.to_bech32m_string(Network::Dash); + let p2sh_enc = p2sh.to_bech32m_string(Network::Dash); + let orchard_enc = orchard.to_bech32m_string(Network::Dash); + + // All three start with "dash1" but have different type-byte characters + assert!(p2pkh_enc.starts_with("dash1k"), "P2PKH: {}", p2pkh_enc); + assert!(p2sh_enc.starts_with("dash1s"), "P2SH: {}", p2sh_enc); + assert!( + orchard_enc.starts_with("dash1z"), + "Orchard: {}", + orchard_enc + ); + + // Cross-decoding should fail + assert!(PlatformAddress::from_bech32m_string(&orchard_enc).is_err()); + assert!(OrchardAddress::from_bech32m_string(&p2pkh_enc).is_err()); + } + + #[test] + fn test_orchard_address_from_raw_bytes_invalid_pk_d() { + // All zeros for pk_d is not a valid Pallas curve point + let mut raw = [0u8; 43]; + raw[0] = 0x01; // non-zero diversifier + assert!(OrchardAddress::from_raw_bytes(&raw).is_err()); + } + + #[test] + fn test_orchard_address_display() { + let address = test_orchard_address(); + let display = format!("{}", address); + assert!(display.starts_with("Orchard(d=")); + assert!(display.contains("pk_d=")); + } +} diff --git a/packages/rs-dpp/src/address_funds/platform_address.rs b/packages/rs-dpp/src/address_funds/platform_address.rs index f893f2a4370..3775accefc9 100644 --- a/packages/rs-dpp/src/address_funds/platform_address.rs +++ b/packages/rs-dpp/src/address_funds/platform_address.rs @@ -555,170 +555,6 @@ impl PlatformAddress { } } -// --------------------------------------------------------------------------- -// Orchard shielded payment address (requires `shielded-tx`) -// --------------------------------------------------------------------------- - -/// Size of the Orchard diversifier (11 bytes). -#[cfg(feature = "shielded-tx")] -pub const ORCHARD_DIVERSIFIER_SIZE: usize = 11; -/// Size of the Orchard diversified transmission key pk_d (32 bytes, Pallas curve point). -#[cfg(feature = "shielded-tx")] -pub const ORCHARD_PKD_SIZE: usize = 32; -/// Total size of a raw Orchard payment address (43 bytes = diversifier + pk_d). -#[cfg(feature = "shielded-tx")] -pub const ORCHARD_ADDRESS_SIZE: usize = ORCHARD_DIVERSIFIER_SIZE + ORCHARD_PKD_SIZE; - -/// An Orchard shielded payment address. -/// -/// Composed of a diversifier (11 bytes) and a diversified transmission key (32 bytes). -/// The diversifier enables a single spending key to derive an unlimited number of -/// unlinkable payment addresses. Only the holder of the corresponding FullViewingKey -/// (or IncomingViewingKey) can link diversified addresses to the same wallet. -/// -/// Bech32m encoding uses type byte `0x10`, producing addresses that start with `z`: -/// - Mainnet: `dash1z...` -/// - Testnet: `tdash1z...` -/// -/// The raw Orchard address format matches Zcash Orchard (43 bytes), but the -/// string encoding is Dash-specific (no F4Jumble, no Unified Address wrapper). -/// -/// Wraps `grovedb_commitment_tree::PaymentAddress`. Use [`From`] -/// to convert from the orchard crate's native type, or [`inner()`](OrchardAddress::inner) -/// / [`into_inner()`](OrchardAddress::into_inner) to access the wrapped address. -/// -/// Requires the `shielded-tx` feature. -#[cfg(feature = "shielded-tx")] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct OrchardAddress(grovedb_commitment_tree::PaymentAddress); - -#[cfg(feature = "shielded-tx")] -impl OrchardAddress { - /// Type byte for Orchard addresses in bech32m encoding (user-facing). - /// Produces 'z' as the first bech32 character. - pub const ORCHARD_TYPE: u8 = 0x10; - - /// Returns the inner [`PaymentAddress`](grovedb_commitment_tree::PaymentAddress). - pub fn inner(&self) -> &grovedb_commitment_tree::PaymentAddress { - &self.0 - } - - /// Consumes the wrapper and returns the inner `PaymentAddress`. - pub fn into_inner(self) -> grovedb_commitment_tree::PaymentAddress { - self.0 - } - - /// Creates an OrchardAddress from a 43-byte raw address. - /// - /// The first 11 bytes are the diversifier, the next 32 are pk_d. - /// Returns an error if `pk_d` is not a valid Pallas curve point. - pub fn from_raw_bytes(bytes: &[u8; ORCHARD_ADDRESS_SIZE]) -> Result { - let addr = - Option::from(grovedb_commitment_tree::PaymentAddress::from_raw_address_bytes(bytes)) - .ok_or_else(|| { - ProtocolError::DecodingError( - "OrchardAddress pk_d is not a valid Pallas curve point".to_string(), - ) - })?; - Ok(Self(addr)) - } - - /// Returns the raw 43-byte address (diversifier || pk_d). - pub fn to_raw_bytes(&self) -> [u8; ORCHARD_ADDRESS_SIZE] { - self.0.to_raw_address_bytes() - } - - /// Encodes the OrchardAddress as a bech32m string for the specified network. - /// - /// Format: `1` - /// - Data: type_byte (0x10) || diversifier (11 bytes) || pk_d (32 bytes) - /// - Total payload: 44 bytes - /// - Checksum: bech32m (BIP-350) - pub fn to_bech32m_string(&self, network: Network) -> String { - let hrp_str = PlatformAddress::hrp_for_network(network); - let hrp = Hrp::parse(hrp_str).expect("HRP is valid"); - - let raw = self.to_raw_bytes(); - let mut payload = Vec::with_capacity(1 + ORCHARD_ADDRESS_SIZE); - payload.push(Self::ORCHARD_TYPE); - payload.extend_from_slice(&raw); - - bech32::encode::(hrp, &payload).expect("encoding should succeed") - } - - /// Decodes a bech32m-encoded Orchard address string. - /// - /// # Returns - /// - `Ok((OrchardAddress, Network))` - The decoded address and its network - /// - `Err(ProtocolError)` - If the address is invalid - pub fn from_bech32m_string(s: &str) -> Result<(Self, Network), ProtocolError> { - let (hrp, data) = - bech32::decode(s).map_err(|e| ProtocolError::DecodingError(format!("{}", e)))?; - - let hrp_lower = hrp.as_str().to_ascii_lowercase(); - let network = match hrp_lower.as_str() { - s if s == PLATFORM_HRP_MAINNET => Network::Dash, - s if s == PLATFORM_HRP_TESTNET => Network::Testnet, - _ => { - return Err(ProtocolError::DecodingError(format!( - "invalid HRP '{}': expected '{}' or '{}'", - hrp, PLATFORM_HRP_MAINNET, PLATFORM_HRP_TESTNET - ))) - } - }; - - // Validate payload: 1 type byte + 11 diversifier + 32 pk_d = 44 bytes - if data.len() != 1 + ORCHARD_ADDRESS_SIZE { - return Err(ProtocolError::DecodingError(format!( - "invalid Orchard address length: expected {} bytes, got {}", - 1 + ORCHARD_ADDRESS_SIZE, - data.len() - ))); - } - - if data[0] != Self::ORCHARD_TYPE { - return Err(ProtocolError::DecodingError(format!( - "invalid Orchard address type byte: expected 0x{:02x}, got 0x{:02x}", - Self::ORCHARD_TYPE, - data[0] - ))); - } - - let mut raw = [0u8; ORCHARD_ADDRESS_SIZE]; - raw.copy_from_slice(&data[1..]); - Self::from_raw_bytes(&raw).map(|addr| (addr, network)) - } -} - -/// Infallible conversion from the orchard crate's `PaymentAddress` to `OrchardAddress`. -#[cfg(feature = "shielded-tx")] -impl From for OrchardAddress { - fn from(addr: grovedb_commitment_tree::PaymentAddress) -> Self { - Self(addr) - } -} - -/// Infallible conversion from a reference to `PaymentAddress`. -#[cfg(feature = "shielded-tx")] -impl From<&grovedb_commitment_tree::PaymentAddress> for OrchardAddress { - fn from(addr: &grovedb_commitment_tree::PaymentAddress) -> Self { - Self(*addr) - } -} - -#[cfg(feature = "shielded-tx")] -impl std::fmt::Display for OrchardAddress { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let raw = self.to_raw_bytes(); - write!( - f, - "Orchard(d={}, pk_d={})", - hex::encode(&raw[..ORCHARD_DIVERSIFIER_SIZE]), - hex::encode(&raw[ORCHARD_DIVERSIFIER_SIZE..]) - ) - } -} - impl std::fmt::Display for PlatformAddress { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -1477,141 +1313,4 @@ mod tests { assert_eq!(p2pkh_decoded, p2pkh); assert_eq!(p2sh_decoded, p2sh); } - - // ======================== - // Orchard address tests (require shielded-tx feature) - // ======================== - - #[cfg(feature = "shielded-tx")] - fn test_orchard_address() -> OrchardAddress { - use grovedb_commitment_tree::{FullViewingKey, Scope, SpendingKey}; - let sk = SpendingKey::from_bytes([42u8; 32]).unwrap(); - let fvk = FullViewingKey::from(&sk); - let payment_address = fvk.address_at(0u32, Scope::External); - OrchardAddress::from(payment_address) - } - - #[cfg(feature = "shielded-tx")] - #[test] - fn test_orchard_address_raw_bytes_roundtrip() { - let address = test_orchard_address(); - let raw = address.to_raw_bytes(); - assert_eq!(raw.len(), 43); - - let recovered = OrchardAddress::from_raw_bytes(&raw).unwrap(); - assert_eq!(recovered, address); - } - - #[cfg(feature = "shielded-tx")] - #[test] - fn test_orchard_bech32m_mainnet_roundtrip() { - let address = test_orchard_address(); - - let encoded = address.to_bech32m_string(Network::Dash); - assert!( - encoded.starts_with("dash1z"), - "Orchard mainnet address should start with 'dash1z', got: {}", - encoded - ); - - let (decoded, network) = - OrchardAddress::from_bech32m_string(&encoded).expect("decoding should succeed"); - assert_eq!(decoded, address); - assert_eq!(network, Network::Dash); - } - - #[cfg(feature = "shielded-tx")] - #[test] - fn test_orchard_bech32m_testnet_roundtrip() { - let address = test_orchard_address(); - - let encoded = address.to_bech32m_string(Network::Testnet); - assert!( - encoded.starts_with("tdash1z"), - "Orchard testnet address should start with 'tdash1z', got: {}", - encoded - ); - - let (decoded, network) = - OrchardAddress::from_bech32m_string(&encoded).expect("decoding should succeed"); - assert_eq!(decoded, address); - assert_eq!(network, Network::Testnet); - } - - #[cfg(feature = "shielded-tx")] - #[test] - fn test_orchard_bech32m_wrong_type_byte_fails() { - // Manually construct an address with P2PKH type byte (0xb0) but 44-byte payload - let hrp = Hrp::parse("dash").unwrap(); - let mut payload = vec![PlatformAddress::P2PKH_TYPE]; // Wrong type byte - payload.extend_from_slice(&[0u8; 43]); - let encoded = bech32::encode::(hrp, &payload).unwrap(); - - let result = OrchardAddress::from_bech32m_string(&encoded); - assert!(result.is_err()); - assert!(result - .unwrap_err() - .to_string() - .contains("invalid Orchard address type byte")); - } - - #[cfg(feature = "shielded-tx")] - #[test] - fn test_orchard_bech32m_wrong_length_fails() { - // Too short (only 20 bytes instead of 43) - let hrp = Hrp::parse("dash").unwrap(); - let mut payload = vec![OrchardAddress::ORCHARD_TYPE]; - payload.extend_from_slice(&[0u8; 20]); - let encoded = bech32::encode::(hrp, &payload).unwrap(); - - let result = OrchardAddress::from_bech32m_string(&encoded); - assert!(result.is_err()); - assert!(result - .unwrap_err() - .to_string() - .contains("invalid Orchard address length")); - } - - #[cfg(feature = "shielded-tx")] - #[test] - fn test_orchard_and_platform_addresses_are_distinguishable() { - let p2pkh = PlatformAddress::P2pkh([0xAB; 20]); - let p2sh = PlatformAddress::P2sh([0xAB; 20]); - let orchard = test_orchard_address(); - - let p2pkh_enc = p2pkh.to_bech32m_string(Network::Dash); - let p2sh_enc = p2sh.to_bech32m_string(Network::Dash); - let orchard_enc = orchard.to_bech32m_string(Network::Dash); - - // All three start with "dash1" but have different type-byte characters - assert!(p2pkh_enc.starts_with("dash1k"), "P2PKH: {}", p2pkh_enc); - assert!(p2sh_enc.starts_with("dash1s"), "P2SH: {}", p2sh_enc); - assert!( - orchard_enc.starts_with("dash1z"), - "Orchard: {}", - orchard_enc - ); - - // Cross-decoding should fail - assert!(PlatformAddress::from_bech32m_string(&orchard_enc).is_err()); - assert!(OrchardAddress::from_bech32m_string(&p2pkh_enc).is_err()); - } - - #[cfg(feature = "shielded-tx")] - #[test] - fn test_orchard_address_from_raw_bytes_invalid_pk_d() { - // All zeros for pk_d is not a valid Pallas curve point - let mut raw = [0u8; 43]; - raw[0] = 0x01; // non-zero diversifier - assert!(OrchardAddress::from_raw_bytes(&raw).is_err()); - } - - #[cfg(feature = "shielded-tx")] - #[test] - fn test_orchard_address_display() { - let address = test_orchard_address(); - let display = format!("{}", address); - assert!(display.starts_with("Orchard(d=")); - assert!(display.contains("pk_d=")); - } } diff --git a/packages/rs-dpp/src/serialization/mod.rs b/packages/rs-dpp/src/serialization/mod.rs index bc5db88925f..ab497baffd7 100644 --- a/packages/rs-dpp/src/serialization/mod.rs +++ b/packages/rs-dpp/src/serialization/mod.rs @@ -1,2 +1,4 @@ +#[cfg(feature = "state-transition-serde-conversion")] +pub(crate) mod serde_bytes_64; pub(crate) mod serialization_traits; pub use serialization_traits::*; diff --git a/packages/rs-dpp/src/serialization/serde_bytes_64.rs b/packages/rs-dpp/src/serialization/serde_bytes_64.rs new file mode 100644 index 00000000000..6fc830deb30 --- /dev/null +++ b/packages/rs-dpp/src/serialization/serde_bytes_64.rs @@ -0,0 +1,33 @@ +//! Serde helper for `[u8; 64]` fields. +//! +//! Serde's built-in array support only covers arrays up to 32 elements. +//! This module provides custom serialize/deserialize for 64-byte arrays +//! (e.g. RedPallas binding signatures, spend auth signatures). +//! +//! - **Human-readable** formats (JSON): hex-encoded string +//! - **Binary** formats (bincode, CBOR): raw byte sequence + +use serde::{Deserialize, Deserializer, Serializer}; + +pub fn serialize(bytes: &[u8; 64], serializer: S) -> Result { + if serializer.is_human_readable() { + serializer.serialize_str(&hex::encode(bytes)) + } else { + serializer.serialize_bytes(bytes) + } +} + +pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<[u8; 64], D::Error> { + if deserializer.is_human_readable() { + let s = ::deserialize(deserializer)?; + let vec = hex::decode(&s).map_err(serde::de::Error::custom)?; + vec.try_into().map_err(|v: Vec| { + serde::de::Error::custom(format!("expected 64 bytes, got {}", v.len())) + }) + } else { + let vec = >::deserialize(deserializer)?; + vec.try_into().map_err(|v: Vec| { + serde::de::Error::custom(format!("expected 64 bytes, got {}", v.len())) + }) + } +} diff --git a/packages/rs-dpp/src/shielded/mod.rs b/packages/rs-dpp/src/shielded/mod.rs index 2e4454c1821..aa399632a96 100644 --- a/packages/rs-dpp/src/shielded/mod.rs +++ b/packages/rs-dpp/src/shielded/mod.rs @@ -64,23 +64,6 @@ pub fn compute_minimum_shielded_fee( constants.shielded_proof_verification_fee + num_actions as u64 * per_action } -/// Serde helper for `[u8; 64]` fields (serde only supports arrays up to 32). -#[cfg(feature = "state-transition-serde-conversion")] -pub(crate) mod serde_bytes_64 { - use serde::{Deserialize, Deserializer, Serializer}; - - pub fn serialize(bytes: &[u8; 64], serializer: S) -> Result { - serializer.serialize_bytes(bytes) - } - - pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<[u8; 64], D::Error> { - let vec = >::deserialize(deserializer)?; - vec.try_into().map_err(|v: Vec| { - serde::de::Error::custom(format!("expected 64 bytes, got {}", v.len())) - }) - } -} - /// Common Orchard bundle parameters shared across all shielded transition types. /// /// Groups the fields that every shielded transition carries identically: @@ -167,7 +150,7 @@ pub struct SerializedAction { /// signature from one transition cannot be reused in another. #[cfg_attr( feature = "state-transition-serde-conversion", - serde(with = "serde_bytes_64") + serde(with = "crate::serialization::serde_bytes_64") )] pub spend_auth_sig: [u8; 64], } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs index 7492a91551c..dd65db3d64a 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs @@ -45,7 +45,7 @@ pub struct ShieldFromAssetLockTransitionV0 { /// RedPallas binding signature #[cfg_attr( feature = "state-transition-serde-conversion", - serde(with = "crate::shielded::serde_bytes_64") + serde(with = "crate::serialization::serde_bytes_64") )] pub binding_signature: [u8; 64], /// ECDSA signature over the signable bytes (excluded from sig hash) diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/mod.rs index 2ad25b20f55..0ded39fb887 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/mod.rs @@ -49,7 +49,7 @@ pub struct ShieldTransitionV0 { /// RedPallas binding signature #[cfg_attr( feature = "state-transition-serde-conversion", - serde(with = "crate::shielded::serde_bytes_64") + serde(with = "crate::serialization::serde_bytes_64") )] pub binding_signature: [u8; 64], /// Fee payment strategy diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/mod.rs index d973cb2607a..7ee5c1e8cc2 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/mod.rs @@ -39,7 +39,7 @@ pub struct ShieldedTransferTransitionV0 { /// RedPallas binding signature #[cfg_attr( feature = "state-transition-serde-conversion", - serde(with = "crate::shielded::serde_bytes_64") + serde(with = "crate::serialization::serde_bytes_64") )] pub binding_signature: [u8; 64], } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs index 0f436aa744a..f8f0615d001 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs @@ -41,7 +41,7 @@ pub struct ShieldedWithdrawalTransitionV0 { /// RedPallas binding signature #[cfg_attr( feature = "state-transition-serde-conversion", - serde(with = "crate::shielded::serde_bytes_64") + serde(with = "crate::serialization::serde_bytes_64") )] pub binding_signature: [u8; 64], /// Core transaction fee rate diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs index 4800b7c0562..08544d5752a 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs @@ -42,7 +42,7 @@ pub struct UnshieldTransitionV0 { /// RedPallas binding signature #[cfg_attr( feature = "state-transition-serde-conversion", - serde(with = "crate::shielded::serde_bytes_64") + serde(with = "crate::serialization::serde_bytes_64") )] pub binding_signature: [u8; 64], } From dba048f6df1b61a8bd2ea01d590d9773173a232f Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 5 Mar 2026 20:53:43 +0700 Subject: [PATCH 26/30] refactor(dpp): remove Option wrapper from common shielded validation helpers Change `validate_actions_count`, `validate_proof_not_empty`, and `validate_anchor_not_zero` to return `SimpleConsensusValidationResult` directly instead of `Option`. Update all 5 call sites to use `if !result.is_valid()` pattern. Co-Authored-By: Claude Opus 4.6 --- .../shielded/common_validation.rs | 28 +++++++++---------- .../v0/state_transition_validation.rs | 17 ++++++----- .../v0/state_transition_validation.rs | 17 ++++++----- .../v0/state_transition_validation.rs | 17 ++++++----- .../v0/state_transition_validation.rs | 17 ++++++----- .../v0/state_transition_validation.rs | 17 ++++++----- 6 files changed, 64 insertions(+), 49 deletions(-) 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 c18f5084c89..9daf7c0e598 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 @@ -10,42 +10,42 @@ use crate::validation::SimpleConsensusValidationResult; pub fn validate_actions_count( actions: &[SerializedAction], max_actions: u16, -) -> Option { +) -> SimpleConsensusValidationResult { if actions.is_empty() { - Some(SimpleConsensusValidationResult::new_with_error( + SimpleConsensusValidationResult::new_with_error( BasicError::ShieldedNoActionsError(ShieldedNoActionsError::new()).into(), - )) + ) } else if actions.len() > max_actions as usize { - Some(SimpleConsensusValidationResult::new_with_error( + SimpleConsensusValidationResult::new_with_error( BasicError::ShieldedTooManyActionsError(ShieldedTooManyActionsError::new( actions.len().min(u16::MAX as usize) as u16, max_actions, )) .into(), - )) + ) } else { - None + SimpleConsensusValidationResult::new() } } /// Validate that the proof is not empty. -pub fn validate_proof_not_empty(proof: &[u8]) -> Option { +pub fn validate_proof_not_empty(proof: &[u8]) -> SimpleConsensusValidationResult { if proof.is_empty() { - Some(SimpleConsensusValidationResult::new_with_error( + SimpleConsensusValidationResult::new_with_error( BasicError::ShieldedEmptyProofError(ShieldedEmptyProofError::new()).into(), - )) + ) } else { - None + SimpleConsensusValidationResult::new() } } /// Validate that the anchor is not all zeros (for transitions that consume notes). -pub fn validate_anchor_not_zero(anchor: &[u8; 32]) -> Option { +pub fn validate_anchor_not_zero(anchor: &[u8; 32]) -> SimpleConsensusValidationResult { if *anchor == [0u8; 32] { - Some(SimpleConsensusValidationResult::new_with_error( + SimpleConsensusValidationResult::new_with_error( BasicError::ShieldedZeroAnchorError(ShieldedZeroAnchorError::new()).into(), - )) + ) } else { - None + SimpleConsensusValidationResult::new() } } 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 84ca071d606..d29bf1316c5 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 @@ -14,13 +14,14 @@ impl StateTransitionStructureValidation for ShieldFromAssetLockTransitionV0 { platform_version: &PlatformVersion, ) -> SimpleConsensusValidationResult { // Actions count must be in [1, max] - if let Some(err) = validate_actions_count( + let result = validate_actions_count( &self.actions, platform_version .system_limits .max_shielded_transition_actions, - ) { - return err; + ); + if !result.is_valid() { + return result; } // value_balance must be > 0 (credits flowing into pool) @@ -48,13 +49,15 @@ impl StateTransitionStructureValidation for ShieldFromAssetLockTransitionV0 { } // Proof must not be empty - if let Some(err) = validate_proof_not_empty(&self.proof) { - return err; + let result = validate_proof_not_empty(&self.proof); + if !result.is_valid() { + return result; } // Anchor must not be all zeros - if let Some(err) = validate_anchor_not_zero(&self.anchor) { - return err; + let result = validate_anchor_not_zero(&self.anchor); + if !result.is_valid() { + return result; } SimpleConsensusValidationResult::new() 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 9240ff676e8..ff1b4805936 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 @@ -19,13 +19,14 @@ impl StateTransitionStructureValidation for ShieldTransitionV0 { platform_version: &PlatformVersion, ) -> SimpleConsensusValidationResult { // Actions count must be in [1, max] - if let Some(err) = validate_actions_count( + let result = validate_actions_count( &self.actions, platform_version .system_limits .max_shielded_transition_actions, - ) { - return err; + ); + if !result.is_valid() { + return result; } // Inputs must not be empty (shield requires address funding) @@ -77,13 +78,15 @@ impl StateTransitionStructureValidation for ShieldTransitionV0 { } // Proof must not be empty - if let Some(err) = validate_proof_not_empty(&self.proof) { - return err; + let result = validate_proof_not_empty(&self.proof); + if !result.is_valid() { + return result; } // Anchor must not be all zeros - if let Some(err) = validate_anchor_not_zero(&self.anchor) { - return err; + let result = validate_anchor_not_zero(&self.anchor); + if !result.is_valid() { + return result; } // Fee strategy validation (reuse address funds patterns) 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 39960ef3815..7329f6c8706 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 @@ -14,13 +14,14 @@ impl StateTransitionStructureValidation for ShieldedTransferTransitionV0 { platform_version: &PlatformVersion, ) -> SimpleConsensusValidationResult { // Actions count must be in [1, max] - if let Some(err) = validate_actions_count( + let result = validate_actions_count( &self.actions, platform_version .system_limits .max_shielded_transition_actions, - ) { - return err; + ); + if !result.is_valid() { + return result; } // value_balance must fit in i64 (required for Orchard protocol) @@ -36,13 +37,15 @@ impl StateTransitionStructureValidation for ShieldedTransferTransitionV0 { } // Proof must not be empty - if let Some(err) = validate_proof_not_empty(&self.proof) { - return err; + let result = validate_proof_not_empty(&self.proof); + if !result.is_valid() { + return result; } // Anchor must not be all zeros - if let Some(err) = validate_anchor_not_zero(&self.anchor) { - return err; + let result = validate_anchor_not_zero(&self.anchor); + if !result.is_valid() { + return result; } SimpleConsensusValidationResult::new() 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 899c332784d..41821b3e258 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 @@ -14,13 +14,14 @@ impl StateTransitionStructureValidation for ShieldedWithdrawalTransitionV0 { platform_version: &PlatformVersion, ) -> SimpleConsensusValidationResult { // Actions count must be in [1, max] - if let Some(err) = validate_actions_count( + let result = validate_actions_count( &self.actions, platform_version .system_limits .max_shielded_transition_actions, - ) { - return err; + ); + if !result.is_valid() { + return result; } // unshielding_amount must be positive and within i64::MAX @@ -48,13 +49,15 @@ impl StateTransitionStructureValidation for ShieldedWithdrawalTransitionV0 { } // Proof must not be empty - if let Some(err) = validate_proof_not_empty(&self.proof) { - return err; + let result = validate_proof_not_empty(&self.proof); + if !result.is_valid() { + return result; } // Anchor must not be all zeros - if let Some(err) = validate_anchor_not_zero(&self.anchor) { - return err; + let result = validate_anchor_not_zero(&self.anchor); + if !result.is_valid() { + return result; } SimpleConsensusValidationResult::new() 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 9b26a0f760f..8715b82cd67 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 @@ -14,13 +14,14 @@ impl StateTransitionStructureValidation for UnshieldTransitionV0 { platform_version: &PlatformVersion, ) -> SimpleConsensusValidationResult { // Actions count must be in [1, max] - if let Some(err) = validate_actions_count( + let result = validate_actions_count( &self.actions, platform_version .system_limits .max_shielded_transition_actions, - ) { - return err; + ); + if !result.is_valid() { + return result; } // unshielding_amount must be positive and within i64::MAX @@ -47,13 +48,15 @@ impl StateTransitionStructureValidation for UnshieldTransitionV0 { } // Proof must not be empty - if let Some(err) = validate_proof_not_empty(&self.proof) { - return err; + let result = validate_proof_not_empty(&self.proof); + if !result.is_valid() { + return result; } // Anchor must not be all zeros - if let Some(err) = validate_anchor_not_zero(&self.anchor) { - return err; + let result = validate_anchor_not_zero(&self.anchor); + if !result.is_valid() { + return result; } SimpleConsensusValidationResult::new() From a9ad9445b9c43f196698f7e886d8f9d2a7a1d595 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 6 Mar 2026 13:24:39 +0700 Subject: [PATCH 27/30] refactor(dpp): derive Serialize/Deserialize for asset lock types, remove serde(skip) Add serde derives to StoredAssetLockInfo, AssetLockValue, and AssetLockValueV0 so the VerifiedAssetLockConsumed variant no longer needs to be skipped during serialization. Co-Authored-By: Claude Opus 4.6 --- packages/rs-dpp/src/asset_lock/mod.rs | 2 +- packages/rs-dpp/src/asset_lock/reduced_asset_lock_value/mod.rs | 2 +- .../rs-dpp/src/asset_lock/reduced_asset_lock_value/v0/mod.rs | 2 +- packages/rs-dpp/src/state_transition/proof_result.rs | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/rs-dpp/src/asset_lock/mod.rs b/packages/rs-dpp/src/asset_lock/mod.rs index 3844bfc5933..3fa57ebb2c3 100644 --- a/packages/rs-dpp/src/asset_lock/mod.rs +++ b/packages/rs-dpp/src/asset_lock/mod.rs @@ -6,7 +6,7 @@ pub type PastAssetLockStateTransitionHashes = Vec>; /// An enumeration of the possible states when querying platform to get the stored state of an outpoint /// representing if the asset lock was already used or not. -#[derive(Debug)] +#[derive(Debug, serde::Serialize, serde::Deserialize)] pub enum StoredAssetLockInfo { /// The asset lock was fully consumed in the past FullyConsumed, diff --git a/packages/rs-dpp/src/asset_lock/reduced_asset_lock_value/mod.rs b/packages/rs-dpp/src/asset_lock/reduced_asset_lock_value/mod.rs index 5b18311013a..48f811e52b1 100644 --- a/packages/rs-dpp/src/asset_lock/reduced_asset_lock_value/mod.rs +++ b/packages/rs-dpp/src/asset_lock/reduced_asset_lock_value/mod.rs @@ -11,7 +11,7 @@ mod v0; pub use v0::{AssetLockValueGettersV0, AssetLockValueSettersV0}; -#[derive(Debug, Clone, Encode, Decode, PlatformSerialize, PlatformDeserialize, From, PartialEq)] +#[derive(Debug, Clone, Encode, Decode, PlatformSerialize, PlatformDeserialize, From, PartialEq, serde::Serialize, serde::Deserialize)] #[platform_serialize(unversioned)] pub enum AssetLockValue { V0(AssetLockValueV0), diff --git a/packages/rs-dpp/src/asset_lock/reduced_asset_lock_value/v0/mod.rs b/packages/rs-dpp/src/asset_lock/reduced_asset_lock_value/v0/mod.rs index bc733e03f48..6ad46de91e8 100644 --- a/packages/rs-dpp/src/asset_lock/reduced_asset_lock_value/v0/mod.rs +++ b/packages/rs-dpp/src/asset_lock/reduced_asset_lock_value/v0/mod.rs @@ -2,7 +2,7 @@ use crate::fee::Credits; use bincode::{Decode, Encode}; use platform_value::Bytes32; -#[derive(Debug, Clone, Encode, Decode, PartialEq)] +#[derive(Debug, Clone, Encode, Decode, PartialEq, serde::Serialize, serde::Deserialize)] pub struct AssetLockValueV0 { pub(super) initial_credit_value: Credits, pub(super) tx_out_script: Vec, diff --git a/packages/rs-dpp/src/state_transition/proof_result.rs b/packages/rs-dpp/src/state_transition/proof_result.rs index d36e14e0380..0dcd6a661a2 100644 --- a/packages/rs-dpp/src/state_transition/proof_result.rs +++ b/packages/rs-dpp/src/state_transition/proof_result.rs @@ -56,7 +56,6 @@ pub enum StateTransitionProofResult { PartialIdentity, BTreeMap>, ), - #[cfg_attr(feature = "state-transition-serde-conversion", serde(skip))] VerifiedAssetLockConsumed(StoredAssetLockInfo), VerifiedShieldedNullifiers(Vec<(Vec, bool)>), VerifiedShieldedNullifiersWithAddressInfos( From c21b5ff7ec7c651812048f4160f781ce4bd37a35 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 6 Mar 2026 14:17:28 +0700 Subject: [PATCH 28/30] refactor(dpp): rename shielded-tx feature to shielded-client --- packages/rs-dpp/Cargo.toml | 2 +- packages/rs-dpp/src/address_funds/mod.rs | 4 ++-- packages/rs-dpp/src/shielded/builder/mod.rs | 2 +- packages/rs-dpp/src/shielded/mod.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/rs-dpp/Cargo.toml b/packages/rs-dpp/Cargo.toml index 78f36d959fc..c59508c2709 100644 --- a/packages/rs-dpp/Cargo.toml +++ b/packages/rs-dpp/Cargo.toml @@ -328,7 +328,7 @@ extended-document = [ ] token-reward-explanations = ["dep:chrono-tz"] -shielded-tx = [ +shielded-client = [ "state-transition-signing", "dep:grovedb-commitment-tree", ] diff --git a/packages/rs-dpp/src/address_funds/mod.rs b/packages/rs-dpp/src/address_funds/mod.rs index 15f2e71de4c..979ef7970ec 100644 --- a/packages/rs-dpp/src/address_funds/mod.rs +++ b/packages/rs-dpp/src/address_funds/mod.rs @@ -1,12 +1,12 @@ pub mod fee_strategy; -#[cfg(feature = "shielded-tx")] +#[cfg(feature = "shielded-client")] mod orchard_address; mod platform_address; mod witness; mod witness_verification_operations; pub use fee_strategy::*; -#[cfg(feature = "shielded-tx")] +#[cfg(feature = "shielded-client")] pub use orchard_address::*; pub use platform_address::*; pub use witness::*; diff --git a/packages/rs-dpp/src/shielded/builder/mod.rs b/packages/rs-dpp/src/shielded/builder/mod.rs index 77105a7926c..543c818ffaa 100644 --- a/packages/rs-dpp/src/shielded/builder/mod.rs +++ b/packages/rs-dpp/src/shielded/builder/mod.rs @@ -4,7 +4,7 @@ //! builder configuration, proof generation, signature application, //! and serialization into platform state transitions. //! -//! Requires the `shielded-tx` feature, which pulls in +//! Requires the `shielded-client` feature, which pulls in //! `grovedb-commitment-tree` (and transitively the `orchard` crate). //! //! # Example diff --git a/packages/rs-dpp/src/shielded/mod.rs b/packages/rs-dpp/src/shielded/mod.rs index aa399632a96..58f01096b30 100644 --- a/packages/rs-dpp/src/shielded/mod.rs +++ b/packages/rs-dpp/src/shielded/mod.rs @@ -1,4 +1,4 @@ -#[cfg(feature = "shielded-tx")] +#[cfg(feature = "shielded-client")] pub mod builder; use bincode::{Decode, Encode}; From ef0a569dd10cdac2ff581b97ef65505b6cffa361 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 6 Mar 2026 14:24:56 +0700 Subject: [PATCH 29/30] chore(dpp): cargo fmt --- .../src/asset_lock/reduced_asset_lock_value/mod.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/rs-dpp/src/asset_lock/reduced_asset_lock_value/mod.rs b/packages/rs-dpp/src/asset_lock/reduced_asset_lock_value/mod.rs index 48f811e52b1..7ccabcd9c96 100644 --- a/packages/rs-dpp/src/asset_lock/reduced_asset_lock_value/mod.rs +++ b/packages/rs-dpp/src/asset_lock/reduced_asset_lock_value/mod.rs @@ -11,7 +11,18 @@ mod v0; pub use v0::{AssetLockValueGettersV0, AssetLockValueSettersV0}; -#[derive(Debug, Clone, Encode, Decode, PlatformSerialize, PlatformDeserialize, From, PartialEq, serde::Serialize, serde::Deserialize)] +#[derive( + Debug, + Clone, + Encode, + Decode, + PlatformSerialize, + PlatformDeserialize, + From, + PartialEq, + serde::Serialize, + serde::Deserialize, +)] #[platform_serialize(unversioned)] pub enum AssetLockValue { V0(AssetLockValueV0), From 8633829e9f3dce5596192ff04b4b62cc0a64c44e Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 6 Mar 2026 14:28:02 +0700 Subject: [PATCH 30/30] chore(dpp): document shielded-client feature --- packages/rs-dpp/Cargo.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/rs-dpp/Cargo.toml b/packages/rs-dpp/Cargo.toml index c59508c2709..074329c2a23 100644 --- a/packages/rs-dpp/Cargo.toml +++ b/packages/rs-dpp/Cargo.toml @@ -328,6 +328,10 @@ extended-document = [ ] token-reward-explanations = ["dep:chrono-tz"] +# Gates client-side Orchard helpers (address encoding, bundle building, proving). +# Clients that don't need to create shielded transactions can omit this to avoid +# compiling the Orchard/Halo 2 dependency tree. The shielded state transition +# types themselves are always available — only the client tooling is behind this gate. shielded-client = [ "state-transition-signing", "dep:grovedb-commitment-tree",