From de5da2b7351925d394a050f3ab7ef7a26321b72c Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Thu, 19 Feb 2026 17:23:31 +0530 Subject: [PATCH 1/4] feat: Add DelegateWithActions --- .github/workflows/run-tests.yml | 4 +- Cargo.lock | 27 +- Cargo.toml | 23 +- Makefile | 3 + src/args/delegate.rs | 5 +- src/args/delegate_with_actions.rs | 48 +++ src/args/mod.rs | 2 + src/compact/account_meta.rs | 60 ++++ src/compact/instruction.rs | 39 +++ src/compact/mod.rs | 5 + src/discriminator.rs | 3 + src/encryption/mod.rs | 213 ++++++++++++ src/error.rs | 4 +- .../delegate_with_actions.rs | 302 +++++++++++++++++ src/instruction_builder/mod.rs | 2 + src/lib.rs | 22 +- src/processor/fast/delegate_with_actions.rs | 303 ++++++++++++++++++ src/processor/fast/mod.rs | 2 + src/processor/fast/utils/requires.rs | 28 ++ src/state/utils/try_from_bytes.rs | 10 +- tests/test_delegate_with_actions.rs | 165 ++++++++++ 21 files changed, 1248 insertions(+), 22 deletions(-) create mode 100644 src/args/delegate_with_actions.rs create mode 100644 src/compact/account_meta.rs create mode 100644 src/compact/instruction.rs create mode 100644 src/compact/mod.rs create mode 100644 src/encryption/mod.rs create mode 100644 src/instruction_builder/delegate_with_actions.rs create mode 100644 src/processor/fast/delegate_with_actions.rs create mode 100644 tests/test_delegate_with_actions.rs diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 02e477a0..0edba9bf 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -51,7 +51,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Run fmt - run: cargo fmt -- --check + run: rustup component add --toolchain nightly-x86_64-unknown-linux-gnu rustfmt && cargo +nightly fmt -- --check - name: Run clippy run: cargo clippy -- --deny=warnings @@ -80,7 +80,7 @@ jobs: run: | cargo build - - name: run tests + - name: run tests (cargo test-sbf) run: | export PATH="/home/runner/.local/share/solana/install/active_release/bin:$PATH" cargo test-sbf --features unit_test_config diff --git a/Cargo.lock b/Cargo.lock index ae13c814..2e9c235f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2332,6 +2332,7 @@ dependencies = [ "borsh 1.5.7", "bytemuck", "const-crypto", + "curve25519-dalek 4.1.3", "magicblock-delegation-program", "num_enum", "pinocchio 0.10.1", @@ -2343,6 +2344,7 @@ dependencies = [ "rand 0.8.5", "rkyv", "serde", + "sha2 0.10.8", "solana-address", "solana-curve25519", "solana-program", @@ -2354,6 +2356,7 @@ dependencies = [ "strum 0.27.2", "thiserror 2.0.11", "tokio", + "x25519-dalek", ] [[package]] @@ -3690,9 +3693,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.226" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", @@ -3718,18 +3721,18 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.226" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.226" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -7993,6 +7996,18 @@ dependencies = [ "tap", ] +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek 4.1.3", + "rand_core 0.6.4", + "serde", + "zeroize", +] + [[package]] name = "x509-parser" version = "0.14.0" diff --git a/Cargo.toml b/Cargo.toml index ce1ed002..226cd688 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,21 @@ name = "dlp" [features] no-entrypoint = [] -sdk = ["no-entrypoint"] +sdk = [ + "no-entrypoint", + "dep:solana-sdk", + "dep:rand", + "dep:sha2", + "dep:x25519-dalek", + "dep:curve25519-dalek", + "dep:pinocchio", + "dep:pinocchio-log", + "dep:pinocchio-pubkey", + "dep:pinocchio-system", + "dep:const-crypto", + "dep:rkyv", + "dep:solana-curve25519", +] program = [ "dep:pinocchio", "dep:pinocchio-log", @@ -60,7 +74,12 @@ pinocchio-associated-token-account = { version = "0.3.0" , optional = true } solana-address = { version = "2.0", features = ["bytemuck", "decode", "syscalls", "curve25519", "std"] } # manually resolves the conflict with a pinned version of serde -serde = "=1.0.226" +serde = { version = "1.0.228", features = ["derive"] } +sha2 = { version = "0.10", optional = true } +x25519-dalek = { version = "2", features = ["static_secrets"], optional = true } +curve25519-dalek = { version = "4", optional = true } +solana-sdk = { version = ">=1.16", optional = true } +rand = { version = "=0.8.5", features = ["small_rng"], optional = true } [dev-dependencies] assertables = "9.8.2" diff --git a/Makefile b/Makefile index 52a4976d..1a06852e 100644 --- a/Makefile +++ b/Makefile @@ -4,5 +4,8 @@ build: test: RUST_LOG=off cargo test-sbf --features unit_test_config +lint: + cargo clippy -- --deny=warnings + fmt: cargo +nightly fmt diff --git a/src/args/delegate.rs b/src/args/delegate.rs index 305b5e51..c438625d 100644 --- a/src/args/delegate.rs +++ b/src/args/delegate.rs @@ -1,7 +1,10 @@ use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; use solana_program::pubkey::Pubkey; -#[derive(Default, Debug, BorshSerialize, BorshDeserialize)] +#[derive( + Default, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] pub struct DelegateArgs { /// The frequency at which the validator should commit the account data /// if no commit is triggered by the owning program diff --git a/src/args/delegate_with_actions.rs b/src/args/delegate_with_actions.rs new file mode 100644 index 00000000..ae9aff41 --- /dev/null +++ b/src/args/delegate_with_actions.rs @@ -0,0 +1,48 @@ +use serde::{Deserialize, Serialize}; +use solana_program::{instruction::Instruction, pubkey::Pubkey}; + +use super::DelegateArgs; +use crate::compact; + +#[derive(Debug, Serialize, Deserialize)] +pub struct DelegateWithActionsArgs { + /// Standard delegation parameters. + pub delegate: DelegateArgs, + + /// Compact post-delegation actions. + pub actions: PostDelegationActions, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PostDelegationActions { + /// Number of signer pubkeys in the `pubkeys` prefix. + /// First `signer_count` entries of `pubkeys` are required signers. + pub signer_count: u8, + + /// Shared pubkey table. Account metas and program IDs reference this table by index. + pub pubkeys: Vec, + + /// Instruction payload in compact cleartext or encrypted bytes. + pub instructions: Instructions, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum Instructions { + /// Compact cleartext instructions. + ClearText { + instructions: Vec, + }, + + /// Encrypted compact instruction bytes. + Encrypted { instructions: Vec }, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct DecryptedInstructions { + /// Sender-provided nonce/salt to randomize ciphertext so identical + /// plaintext does not always map to identical encrypted bytes. + pub random_salt: u64, + + /// Decrypted instructions ready for execution. + pub instructions: Vec, +} diff --git a/src/args/mod.rs b/src/args/mod.rs index 89853b11..492f02f4 100644 --- a/src/args/mod.rs +++ b/src/args/mod.rs @@ -2,6 +2,7 @@ mod call_handler; mod commit_state; mod delegate; mod delegate_ephemeral_balance; +mod delegate_with_actions; mod top_up_ephemeral_balance; mod types; mod validator_claim_fees; @@ -11,6 +12,7 @@ pub use call_handler::*; pub use commit_state::*; pub use delegate::*; pub use delegate_ephemeral_balance::*; +pub use delegate_with_actions::*; pub use top_up_ephemeral_balance::*; pub use types::*; pub use validator_claim_fees::*; diff --git a/src/compact/account_meta.rs b/src/compact/account_meta.rs new file mode 100644 index 00000000..fb828e45 --- /dev/null +++ b/src/compact/account_meta.rs @@ -0,0 +1,60 @@ +use serde::{Deserialize, Serialize}; + +const ACCOUNT_INDEX_MASK: u8 = 0b0011_1111; +const SIGNER_MASK: u8 = 0b0100_0000; +const WRITABLE_MASK: u8 = 0b1000_0000; + +/// +/// MAX_PUBKEYS = 64 +/// +pub const MAX_PUBKEYS: u8 = ACCOUNT_INDEX_MASK + 1; + +/// Compact account meta packed into one byte. +/// Bits `0..=5` encode the pubkey-table index (`0..MAX_PUBKEYS-1`). +/// Bit `6` is `is_signer`, and bit `7` is `is_writable`. +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub struct AccountMeta(u8); + +impl AccountMeta { + pub fn new(index: u8, is_signer: bool) -> Self { + Self::try_new(index, is_signer, true).expect("index is out of range") + } + pub fn new_readonly(index: u8, is_signer: bool) -> Self { + Self::try_new(index, is_signer, false).expect("index is out of range") + } + + pub fn try_new( + index: u8, + is_signer: bool, + is_writable: bool, + ) -> Option { + if index >= MAX_PUBKEYS { + return None; + } + let mut packed = index; + if is_signer { + packed |= SIGNER_MASK; + } + if is_writable { + packed |= WRITABLE_MASK; + } + Some(Self(packed)) + } + + pub fn index(self) -> u8 { + self.0 & ACCOUNT_INDEX_MASK + } + + pub fn is_signer(self) -> bool { + (self.0 & SIGNER_MASK) != 0 + } + + pub fn is_writable(self) -> bool { + (self.0 & WRITABLE_MASK) != 0 + } + + pub fn set_index(&mut self, new_index: u8) { + *self = Self::try_new(new_index, self.is_signer(), self.is_writable()) + .expect("index is out of range"); + } +} diff --git a/src/compact/instruction.rs b/src/compact/instruction.rs new file mode 100644 index 00000000..271711ff --- /dev/null +++ b/src/compact/instruction.rs @@ -0,0 +1,39 @@ +use serde::{Deserialize, Serialize}; + +use crate::compact; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Instruction { + pub program_id: u8, + pub accounts: Vec, + pub data: Vec, +} + +impl Instruction { + pub fn from_instruction( + ix: solana_program::instruction::Instruction, + index_of: &mut impl FnMut( + /*account_key*/ solana_program::pubkey::Pubkey, + /*signer*/ bool, + ) -> u8, + ) -> Instruction { + Instruction { + program_id: index_of(ix.program_id, false), + + accounts: ix + .accounts + .iter() + .map(|meta| { + compact::AccountMeta::try_new( + index_of(meta.pubkey, meta.is_signer), + meta.is_signer, + meta.is_writable, + ) + .expect("compact account index must fit in 6 bits") + }) + .collect(), + + data: ix.data, + } + } +} diff --git a/src/compact/mod.rs b/src/compact/mod.rs new file mode 100644 index 00000000..a33bfc45 --- /dev/null +++ b/src/compact/mod.rs @@ -0,0 +1,5 @@ +mod account_meta; +mod instruction; + +pub use account_meta::*; +pub use instruction::*; diff --git a/src/discriminator.rs b/src/discriminator.rs index 1af1d58d..c18410de 100644 --- a/src/discriminator.rs +++ b/src/discriminator.rs @@ -53,6 +53,9 @@ pub enum DlpDiscriminator { /// See [crate::processor::process_commit_finalize_from_buffer] for docs. CommitFinalizeFromBuffer = 22, + + /// See [crate::processor::process_delegate_with_actions] for docs. + DelegateWithActions = 23, } impl DlpDiscriminator { diff --git a/src/encryption/mod.rs b/src/encryption/mod.rs new file mode 100644 index 00000000..e06c4a55 --- /dev/null +++ b/src/encryption/mod.rs @@ -0,0 +1,213 @@ +use curve25519_dalek::{ + edwards::CompressedEdwardsY, montgomery::MontgomeryPoint, +}; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha512}; +use solana_program::hash::hashv; +use x25519_dalek::{PublicKey as X25519Public, StaticSecret as X25519Secret}; + +pub const KEY_LEN: usize = 32; + +#[derive(Debug, thiserror::Error)] +pub enum EncryptionError { + #[error("invalid ed25519 public key for x25519 conversion")] + InvalidEd25519PublicKey, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct EncryptedPayloadV1 { + /// Ephemeral X25519 public key created by the sender for this payload. + pub ephemeral_pubkey: [u8; KEY_LEN], + + /// Encrypted compact-action bytes. + pub ciphertext: Vec, +} + +/// Convert an Ed25519 public key into an X25519 public key. +pub fn ed25519_pubkey_to_x25519( + ed25519_pubkey: &[u8; KEY_LEN], +) -> Option<[u8; KEY_LEN]> { + let edwards = CompressedEdwardsY(*ed25519_pubkey).decompress()?; + let montgomery: MontgomeryPoint = edwards.to_montgomery(); + Some(montgomery.to_bytes()) +} + +/// Convert an Ed25519 secret key seed into an X25519 secret key. +/// +/// This follows the libsodium-style conversion: +/// SHA-512(seed) then clamp the first 32 bytes. +pub fn ed25519_secret_to_x25519( + ed25519_secret_seed: &[u8; KEY_LEN], +) -> [u8; KEY_LEN] { + let mut h = Sha512::new(); + h.update(ed25519_secret_seed); + let digest = h.finalize(); + + let mut out = [0u8; KEY_LEN]; + out.copy_from_slice(&digest[..KEY_LEN]); + out[0] &= 248; + out[31] &= 127; + out[31] |= 64; + out +} + +/// Convenience helper for SDK usage: derive X25519 secret key bytes from a Solana Keypair. +#[cfg(feature = "sdk")] +pub fn keypair_to_x25519_secret( + keypair: &solana_sdk::signature::Keypair, +) -> [u8; KEY_LEN] { + let keypair_bytes = keypair.to_bytes(); + let mut seed = [0u8; KEY_LEN]; + seed.copy_from_slice(&keypair_bytes[..KEY_LEN]); + ed25519_secret_to_x25519(&seed) +} + +/// High-level API: encrypt for validator using a random ephemeral secret from OS RNG. +#[cfg(feature = "sdk")] +pub fn encrypt( + plaintext: &[u8], + recipient_x25519_pubkey: &[u8; KEY_LEN], +) -> Vec { + use rand::rngs::OsRng; + let ephemeral_secret = X25519Secret::random_from_rng(OsRng).to_bytes(); + encrypt_with_ephemeral( + plaintext, + recipient_x25519_pubkey, + &ephemeral_secret, + ) +} + +/// High-level API: same as above, but starts from recipient Ed25519 pubkey. +#[cfg(feature = "sdk")] +pub fn encrypt_ed25519_recipient( + plaintext: &[u8], + recipient_ed25519_pubkey: &[u8; KEY_LEN], +) -> Result, EncryptionError> { + let recipient_x25519_pubkey = + ed25519_pubkey_to_x25519(recipient_ed25519_pubkey) + .ok_or(EncryptionError::InvalidEd25519PublicKey)?; + Ok(encrypt(plaintext, &recipient_x25519_pubkey)) +} + +/// Encrypt any plaintext bytes for a recipient X25519 public key. +/// +/// The caller supplies an ephemeral secret key (random per message). +/// This function stores the ephemeral public key in the output payload. +/// +/// Example: +/// `let encrypted = encrypt_with_ephemeral(b"hello", &recipient_pubkey, &ephemeral_secret);` +pub fn encrypt_with_ephemeral( + plaintext: &[u8], + recipient_x25519_pubkey: &[u8; KEY_LEN], + ephemeral_x25519_secret: &[u8; KEY_LEN], +) -> Vec { + let sender_secret = X25519Secret::from(*ephemeral_x25519_secret); + let sender_public = X25519Public::from(&sender_secret); + let recipient_public = X25519Public::from(*recipient_x25519_pubkey); + let shared = sender_secret.diffie_hellman(&recipient_public).to_bytes(); + + let mut ciphertext = plaintext.to_vec(); + xor_with_stream(&mut ciphertext, &shared); + + bincode::serialize(&EncryptedPayloadV1 { + ephemeral_pubkey: sender_public.to_bytes(), + ciphertext, + }) + .expect("encrypted payload serialization should not fail") +} + +/// Decrypt serialized encrypted payload bytes back to plaintext bytes. +/// +/// Example: +/// `let plaintext = decrypt(&encrypted, &recipient_x25519_secret)?;` +pub fn decrypt( + encrypted_payload: &[u8], + recipient_x25519_secret: &[u8; KEY_LEN], +) -> Result, bincode::Error> { + let EncryptedPayloadV1 { + ephemeral_pubkey, + mut ciphertext, + } = bincode::deserialize(encrypted_payload)?; + + let recipient_secret = X25519Secret::from(*recipient_x25519_secret); + let sender_public = X25519Public::from(ephemeral_pubkey); + let shared = recipient_secret.diffie_hellman(&sender_public).to_bytes(); + + xor_with_stream(&mut ciphertext, &shared); + Ok(ciphertext) +} + +fn xor_with_stream(data: &mut [u8], shared_secret: &[u8; KEY_LEN]) { + let mut counter: u64 = 0; + let mut offset = 0usize; + + while offset < data.len() { + let block = hashv(&[shared_secret.as_slice(), &counter.to_le_bytes()]) + .to_bytes(); + + for &k in block.iter() { + if offset >= data.len() { + break; + } + data[offset] ^= k; + offset += 1; + } + + counter = counter.wrapping_add(1); + } +} + +#[cfg(test)] +mod tests { + use x25519_dalek::{ + PublicKey as X25519Public, StaticSecret as X25519Secret, + }; + + use super::*; + + #[test] + fn test_ed25519_secret_to_x25519_shape() { + let seed = [7u8; KEY_LEN]; + let secret = ed25519_secret_to_x25519(&seed); + assert_eq!(secret.len(), KEY_LEN); + assert_eq!(secret[0] & 0b0000_0111, 0); + assert_eq!(secret[31] & 0b1000_0000, 0); + assert_eq!(secret[31] & 0b0100_0000, 0b0100_0000); + } + + #[test] + fn test_encrypt_decrypt_roundtrip() { + let validator_secret = X25519Secret::from([11u8; KEY_LEN]); + let validator_public = X25519Public::from(&validator_secret).to_bytes(); + let validator_secret = validator_secret.to_bytes(); + let ephemeral_secret = [22u8; KEY_LEN]; + let plaintext = b"hello compact actions"; + + let encrypted = encrypt_with_ephemeral( + plaintext, + &validator_public, + &ephemeral_secret, + ); + let decrypted = decrypt(&encrypted, &validator_secret).unwrap(); + assert_eq!(decrypted, plaintext); + } + + #[test] + fn test_random_ephemeral_changes_ciphertext() { + let validator_secret = X25519Secret::from([44u8; KEY_LEN]); + let validator_public = X25519Public::from(&validator_secret).to_bytes(); + let plaintext = b"same bytes"; + + let c1 = encrypt_with_ephemeral( + plaintext, + &validator_public, + &[1u8; KEY_LEN], + ); + let c2 = encrypt_with_ephemeral( + plaintext, + &validator_public, + &[2u8; KEY_LEN], + ); + assert_ne!(c1, c2); + } +} diff --git a/src/error.rs b/src/error.rs index ad12d704..9b2802e2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -159,14 +159,14 @@ impl From for ProgramError { } } -#[cfg(not(feature = "sdk"))] +//#[cfg(not(feature = "sdk"))] impl From for pinocchio::error::ProgramError { fn from(e: DlpError) -> Self { pinocchio::error::ProgramError::Custom(e as u32) } } -#[cfg(not(feature = "sdk"))] +//#[cfg(not(feature = "sdk"))] impl pinocchio::error::ToStr for DlpError { fn to_str(&self) -> &'static str { self.into() diff --git a/src/instruction_builder/delegate_with_actions.rs b/src/instruction_builder/delegate_with_actions.rs new file mode 100644 index 00000000..4aeec111 --- /dev/null +++ b/src/instruction_builder/delegate_with_actions.rs @@ -0,0 +1,302 @@ +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, +}; + +use crate::{ + args::{ + DelegateArgs, DelegateWithActionsArgs, Instructions, + PostDelegationActions, + }, + compact::{self}, + discriminator::DlpDiscriminator, + pda::{ + delegate_buffer_pda_from_delegated_account_and_owner_program, + delegation_metadata_pda_from_delegated_account, + delegation_record_pda_from_delegated_account, + }, +}; + +/// Builds a delegate instruction that stores an actions payload. +/// See [crate::processor::process_delegate_with_actions] for docs. +pub fn delegate_with_actions( + payer: Pubkey, + delegated_account: Pubkey, + owner: Option, + delegate: DelegateArgs, + actions: Vec, + private: bool, +) -> Instruction { + let actions = + compact_post_delegation_actions(actions, private, delegate.validator); + + Instruction { + program_id: crate::id(), + + accounts: { + let owner = owner.unwrap_or(system_program::id()); + let delegate_buffer_pda = + delegate_buffer_pda_from_delegated_account_and_owner_program( + &delegated_account, + &owner, + ); + let delegation_record_pda = + delegation_record_pda_from_delegated_account( + &delegated_account, + ); + let delegation_metadata_pda = + delegation_metadata_pda_from_delegated_account( + &delegated_account, + ); + + [ + vec![ + AccountMeta::new(payer, true), + AccountMeta::new(delegated_account, true), + AccountMeta::new_readonly(owner, false), + AccountMeta::new(delegate_buffer_pda, false), + AccountMeta::new(delegation_record_pda, false), + AccountMeta::new(delegation_metadata_pda, false), + AccountMeta::new_readonly(system_program::id(), false), + ], + actions + .pubkeys + .iter() + .take(actions.signer_count as usize) + .map(|signer| AccountMeta::new_readonly(*signer, true)) + .collect(), + ] + .concat() + }, + + data: { + let args = DelegateWithActionsArgs { delegate, actions }; + let mut data = DlpDiscriminator::DelegateWithActions.to_vec(); + data.extend_from_slice(&bincode::serialize(&args).unwrap()); + data + }, + } +} + +fn compact_post_delegation_actions( + instructions: Vec, + private: bool, + validator: Option, +) -> PostDelegationActions { + let mut pubkeys: Vec<(Pubkey, usize, bool)> = Vec::new(); // Vec of (pubkey, index, signer) + + // return index to pubkeys + let mut index_of = |key: Pubkey, signer: bool| -> u8 { + if let Some(index) = + pubkeys.iter().position(|(existing, _, _)| *existing == key) + { + pubkeys[index].2 |= signer; + return index as u8; + } + assert!( + pubkeys.len() < compact::MAX_PUBKEYS as usize, + "delegate_with_actions supports at most {} unique pubkeys", + compact::MAX_PUBKEYS + ); + pubkeys.push((key, pubkeys.len(), signer)); + pubkeys.len() as u8 - 1 + }; + + let compact_instructions = instructions + .into_iter() + .map(|ix| compact::Instruction::from_instruction(ix, &mut index_of)) + .collect(); + + let (pubkeys, compact_instructions, signer_count) = + reorder_signers_first(pubkeys, compact_instructions); + + let compact_payload = if private { + let serialized = bincode::serialize(&compact_instructions) + .expect("compact instruction serialization should not fail"); + let validator = validator + .expect("delegate.validator is required when private is true"); + + #[cfg(feature = "sdk")] + { + Instructions::Encrypted { + instructions: crate::encryption::encrypt_ed25519_recipient( + &serialized, + &validator.to_bytes(), + ) + .expect("validator ed25519 pubkey must convert to x25519"), + } + } + + #[cfg(not(feature = "sdk"))] + { + let _ = (serialized, validator); + panic!("private delegate_with_actions requires sdk feature"); + } + } else { + Instructions::ClearText { + instructions: compact_instructions.clone(), + } + }; + + PostDelegationActions { + signer_count, + pubkeys, + instructions: compact_payload, + } +} + +fn reorder_signers_first( + mut pubkeys: Vec<(Pubkey, usize, bool)>, + mut instructions: Vec, +) -> (Vec, Vec, u8) { + if pubkeys.is_empty() { + return (Vec::new(), instructions, 0); + } + + let signer_count = partition(&mut pubkeys, |(_, _, signer)| *signer); + + let new_index = |old_index: u8| -> u8 { + pubkeys + .iter() + .position(|(_, index, _)| *index == old_index as usize) + .unwrap() as u8 + }; + + for ix in instructions.iter_mut() { + ix.program_id = new_index(ix.program_id); + for meta in ix.accounts.iter_mut() { + meta.set_index(new_index(meta.index())); + } + } + + let pubkeys = pubkeys.into_iter().map(|(key, _, _)| key).collect(); + (pubkeys, instructions, signer_count as u8) +} + +/// +/// It's a C++ equivalent of std::partition() +/// ref: https://en.cppreference.com/w/cpp/algorithm/partition.html +/// +/// Returns the size of first group (good elements) which can also be used as the +/// index of the first element in the second group. +/// +fn partition(v: &mut [T], mut pred: F) -> usize +where + F: FnMut(&T) -> bool, +{ + let mut good = 0; // number of good elements + + for i in 0..v.len() { + if pred(&v[i]) { + v.swap(good, i); + good += 1; + } + } + + good +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_reorder_signers_first_remaps_and_prefixes_signers() { + let a = Pubkey::new_from_array([1; 32]); // 0: signer + let b = Pubkey::new_from_array([2; 32]); // 1: non-signer + let c = Pubkey::new_from_array([3; 32]); // 2: signer + let d = Pubkey::new_from_array([4; 32]); // 3: non-signer + let e = Pubkey::new_from_array([5; 32]); // 4: signer + + // (pubkey, old_index, is_signer) + let pubkeys = vec![ + (a, 0, true), + (b, 1, false), + (c, 2, true), + (d, 3, false), + (e, 4, true), + ]; + let instructions = vec![compact::Instruction { + program_id: 3, // old index of d + accounts: vec![ + compact::AccountMeta::new_readonly(0, true), // a + compact::AccountMeta::new(2, true), // c + compact::AccountMeta::new_readonly(1, false), // b + compact::AccountMeta::new_readonly(4, true), // e + compact::AccountMeta::new(3, false), // d + ], + data: vec![9], + }]; + + let (reordered_pubkeys, ixs, signer_count) = + reorder_signers_first(pubkeys, instructions); + + // reordered: a, c, e, d, b + // 0, 1, 2, 3, 4 + + assert_eq!(signer_count, 3); + assert_eq!(reordered_pubkeys[0], a); // signer + assert_eq!(reordered_pubkeys[1], c); // signer + assert_eq!(reordered_pubkeys[2], e); // signer + assert_eq!(reordered_pubkeys[3], d); // non-signer + assert_eq!(reordered_pubkeys[4], b); // non-signer + + // old->new mapping: a(0)->0, b(1)->4, c(2)->1, d(3)->3, e(4)->2 + // + assert_eq!(ixs[0].program_id, 3); // d + assert_eq!(ixs[0].accounts[0].index(), 0); // a + assert_eq!(ixs[0].accounts[1].index(), 1); // c + assert_eq!(ixs[0].accounts[2].index(), 4); // b + assert_eq!(ixs[0].accounts[3].index(), 2); // e + assert_eq!(ixs[0].accounts[4].index(), 3); // d + } + + #[test] + fn test_compact_post_delegation_actions() { + let a = Pubkey::new_from_array([1; 32]); // 0: signer + let b = Pubkey::new_from_array([2; 32]); // 1: non-signer + let c = Pubkey::new_from_array([3; 32]); // 2: signer + let d = Pubkey::new_from_array([4; 32]); // 3: non-signer + let e = Pubkey::new_from_array([5; 32]); // 4: signer + + let instructions = vec![Instruction { + program_id: d, + accounts: vec![ + AccountMeta::new_readonly(a, true), // a + AccountMeta::new(c, true), // c + AccountMeta::new_readonly(b, false), // b + AccountMeta::new_readonly(e, true), // e + AccountMeta::new(d, false), // d + ], + data: vec![9], + }]; + + let actions = + compact_post_delegation_actions(instructions, false, None); + + // reordered: a, c, e, d, b + // 0, 1, 2, 3, 4 + + assert_eq!(actions.signer_count, 3); + assert_eq!(actions.pubkeys[0], a); // signer + assert_eq!(actions.pubkeys[1], c); // signer + assert_eq!(actions.pubkeys[2], e); // signer + assert_eq!(actions.pubkeys[3], b); // non-signer + assert_eq!(actions.pubkeys[4], d); // non-signer + + // old->new mapping: a(0)->0, b(1)->4, c(2)->1, d(3)->3, e(4)->2 + let Instructions::ClearText { instructions: ixs } = + actions.instructions + else { + panic!(); + }; + + assert_eq!(ixs[0].program_id, 4); // d + assert_eq!(ixs[0].accounts[0].index(), 0); // a + assert_eq!(ixs[0].accounts[1].index(), 1); // c + assert_eq!(ixs[0].accounts[2].index(), 3); // b + assert_eq!(ixs[0].accounts[3].index(), 2); // e + assert_eq!(ixs[0].accounts[4].index(), 4); // d + } +} diff --git a/src/instruction_builder/mod.rs b/src/instruction_builder/mod.rs index 66eef03b..8334b77c 100644 --- a/src/instruction_builder/mod.rs +++ b/src/instruction_builder/mod.rs @@ -10,6 +10,7 @@ mod commit_state; mod commit_state_from_buffer; mod delegate; mod delegate_ephemeral_balance; +mod delegate_with_actions; mod finalize; mod init_protocol_fees_vault; mod init_validator_fees_vault; @@ -32,6 +33,7 @@ pub use commit_state::*; pub use commit_state_from_buffer::*; pub use delegate::*; pub use delegate_ephemeral_balance::*; +pub use delegate_with_actions::*; pub use finalize::*; pub use init_protocol_fees_vault::*; pub use init_validator_fees_vault::*; diff --git a/src/lib.rs b/src/lib.rs index 3e94e488..7a061a7f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,8 +12,10 @@ compile_error!( ); use solana_program::declare_id; + #[cfg(feature = "logging")] use solana_program::msg; + #[cfg(not(feature = "sdk"))] use { crate::discriminator::DlpDiscriminator, @@ -24,6 +26,7 @@ use { }; pub mod args; +pub mod compact; pub mod consts; mod discriminator; pub mod error; @@ -34,18 +37,22 @@ pub mod state; mod account_size_class; +#[cfg(feature = "sdk")] +pub mod encryption; + pub use account_size_class::*; -#[cfg(not(feature = "sdk"))] +//#[cfg(not(feature = "sdk"))] mod diff; -#[cfg(not(feature = "sdk"))] +//#[cfg(not(feature = "sdk"))] mod processor; -#[cfg(not(feature = "sdk"))] +//#[cfg(not(feature = "sdk"))] pub use diff::*; + // re-export -#[cfg(not(feature = "sdk"))] +//#[cfg(not(feature = "sdk"))] pub use rkyv; #[cfg(feature = "log-cost")] @@ -56,7 +63,7 @@ mod entrypoint; declare_id!("DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh"); -#[cfg(not(feature = "sdk"))] +//#[cfg(not(feature = "sdk"))] pub mod fast { pinocchio::address::declare_id!( "DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh" @@ -110,6 +117,11 @@ pub fn fast_process_instruction( program_id, accounts, data, )) } + DlpDiscriminator::DelegateWithActions => { + Some(processor::fast::process_delegate_with_actions( + program_id, accounts, data, + )) + } DlpDiscriminator::CommitState => Some( processor::fast::process_commit_state(program_id, accounts, data), ), diff --git a/src/processor/fast/delegate_with_actions.rs b/src/processor/fast/delegate_with_actions.rs new file mode 100644 index 00000000..4598f917 --- /dev/null +++ b/src/processor/fast/delegate_with_actions.rs @@ -0,0 +1,303 @@ +use pinocchio::{ + address::address_eq, + cpi::{Seed, Signer}, + error::ProgramError, + sysvars::{clock::Clock, Sysvar}, + AccountView, Address, ProgramResult, +}; +use pinocchio_log::log; +use pinocchio_system::instructions as system; + +use crate::{ + args::{DelegateWithActionsArgs, Instructions}, + consts::{DEFAULT_VALIDATOR_IDENTITY, RENT_EXCEPTION_ZERO_BYTES_LAMPORTS}, + error::DlpError, + pda, + processor::{ + fast::{ + to_pinocchio_program_error, + utils::{ + pda::create_pda, + requires::{ + require_owned_pda, require_pda, require_signer, + require_uninitialized_pda, DelegationMetadataCtx, + DelegationRecordCtx, + }, + }, + }, + utils::curve::is_on_curve_fast, + }, + require_n_accounts_with_optionals, + state::{DelegationMetadata, DelegationRecord}, +}; + +/// Delegates an account and stores an actions payload. +pub fn process_delegate_with_actions( + _program_id: &Address, + accounts: &[AccountView], + data: &[u8], +) -> ProgramResult { + let ( + [ + payer, // force multi-line + delegated_account, + owner_program, + delegate_buffer_account, + delegation_record_account, + delegation_metadata_account, + _system_program, + ], + remaining_accounts, + ) = require_n_accounts_with_optionals!(accounts, 7); + + require_owned_pda( + delegated_account, + &crate::fast::ID, + "delegated account", + )?; + + // Check that payer and delegated_account are signers, this ensures the instruction is being called from CPI + require_signer(payer, "payer")?; + require_signer(delegated_account, "delegated account")?; + + // Check that the buffer PDA is initialized and derived correctly from the PDA + require_pda( + delegate_buffer_account, + &[ + pda::DELEGATE_BUFFER_TAG, + delegated_account.address().as_ref(), + ], + owner_program.address(), + true, + "delegate buffer", + )?; + + // Check that the delegation record PDA is uninitialized + let delegation_record_bump = require_uninitialized_pda( + delegation_record_account, + &[ + pda::DELEGATION_RECORD_TAG, + delegated_account.address().as_ref(), + ], + &crate::fast::ID, + true, + DelegationRecordCtx, + )?; + + // Check that the delegation metadata PDA is uninitialized + let delegation_metadata_bump = require_uninitialized_pda( + delegation_metadata_account, + &[ + pda::DELEGATION_METADATA_TAG, + delegated_account.address().as_ref(), + ], + &crate::fast::ID, + true, + DelegationMetadataCtx, + )?; + + // Validate instruction payload shape up-front. This confirms delegate args + // and actions envelope format, while encrypted bytes remain opaque. + let args: DelegateWithActionsArgs = bincode::deserialize(data) + .map_err(|_| ProgramError::InvalidInstructionData)?; + + if args.actions.signer_count as usize > args.actions.pubkeys.len() { + return Err(ProgramError::InvalidInstructionData); + } + + // Validate compact indices for cleartext payload. + if let Instructions::ClearText { instructions } = &args.actions.instructions + { + for compact_ix in instructions { + if compact_ix.program_id as usize >= args.actions.pubkeys.len() { + return Err(ProgramError::InvalidInstructionData); + } + for compact_meta in &compact_ix.accounts { + let pubkey_index = compact_meta.index(); + if pubkey_index as usize >= args.actions.pubkeys.len() { + return Err(ProgramError::InvalidInstructionData); + } + if compact_meta.is_signer() + && pubkey_index >= args.actions.signer_count + { + return Err(ProgramError::InvalidInstructionData); + } + } + } + } + + // Enforce required signers from the pubkey-table prefix. + for signer in &args.actions.pubkeys[..args.actions.signer_count as usize] { + let account = remaining_accounts + .iter() + .find(|account| account.address().to_bytes() == signer.to_bytes()) + .ok_or(ProgramError::NotEnoughAccountKeys)?; + if !account.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + } + + if let Some(validator) = args.delegate.validator { + if validator.to_bytes() == pinocchio_system::ID.to_bytes() { + return Err(DlpError::DelegationToSystemProgramNotAllowed.into()); + } + } + + // Validate seeds if the delegate account is not on curve, i.e. is a PDA + // If the owner is the system program, we check if the account is derived from the delegation program, + // allowing delegation of escrow accounts + if !is_on_curve_fast(delegated_account.address()) { + let program_id = + if address_eq(owner_program.address(), &pinocchio_system::ID) { + &crate::fast::ID + } else { + owner_program.address() + }; + let seeds_to_validate: &[&[u8]] = match args.delegate.seeds.len() { + 1 => &[&args.delegate.seeds[0]], + 2 => &[&args.delegate.seeds[0], &args.delegate.seeds[1]], + 3 => &[ + &args.delegate.seeds[0], + &args.delegate.seeds[1], + &args.delegate.seeds[2], + ], + 4 => &[ + &args.delegate.seeds[0], + &args.delegate.seeds[1], + &args.delegate.seeds[2], + &args.delegate.seeds[3], + ], + 5 => &[ + &args.delegate.seeds[0], + &args.delegate.seeds[1], + &args.delegate.seeds[2], + &args.delegate.seeds[3], + &args.delegate.seeds[4], + ], + 6 => &[ + &args.delegate.seeds[0], + &args.delegate.seeds[1], + &args.delegate.seeds[2], + &args.delegate.seeds[3], + &args.delegate.seeds[4], + &args.delegate.seeds[5], + ], + 7 => &[ + &args.delegate.seeds[0], + &args.delegate.seeds[1], + &args.delegate.seeds[2], + &args.delegate.seeds[3], + &args.delegate.seeds[4], + &args.delegate.seeds[5], + &args.delegate.seeds[6], + ], + 8 => &[ + &args.delegate.seeds[0], + &args.delegate.seeds[1], + &args.delegate.seeds[2], + &args.delegate.seeds[3], + &args.delegate.seeds[4], + &args.delegate.seeds[5], + &args.delegate.seeds[6], + &args.delegate.seeds[7], + ], + _ => return Err(DlpError::TooManySeeds.into()), + }; + let derived_pda = + Address::find_program_address(seeds_to_validate, program_id).0; + + if !address_eq(&derived_pda, delegated_account.address()) { + log!("Expected delegated PDA to be: "); + derived_pda.log(); + log!("but got: "); + delegated_account.address().log(); + return Err(ProgramError::InvalidSeeds); + } + } + + let action_data = bincode::serialize(&args.actions) + .map_err(|_| ProgramError::InvalidInstructionData)?; + + create_pda( + delegation_record_account, + &crate::fast::ID, + DelegationRecord::size_with_discriminator() + action_data.len(), + &[Signer::from(&[ + Seed::from(pda::DELEGATION_RECORD_TAG), + Seed::from(delegated_account.address().as_ref()), + Seed::from(&[delegation_record_bump]), + ])], + payer, + )?; + + // Initialize the delegation record + let delegation_record = DelegationRecord { + owner: owner_program.address().to_bytes().into(), + authority: args + .delegate + .validator + .unwrap_or(DEFAULT_VALIDATOR_IDENTITY), + commit_frequency_ms: args.delegate.commit_frequency_ms as u64, + delegation_slot: Clock::get()?.slot, + lamports: delegated_account.lamports(), + }; + + let mut delegation_record_data = + delegation_record_account.try_borrow_mut()?; + let record_size = DelegationRecord::size_with_discriminator(); + if delegation_record_data.len() != record_size + action_data.len() { + return Err(DlpError::InvalidDataLength.into()); + } + let (record_bytes, action_bytes) = + delegation_record_data.split_at_mut(record_size); + delegation_record + .to_bytes_with_discriminator(record_bytes) + .map_err(to_pinocchio_program_error)?; + action_bytes.copy_from_slice(&action_data); + + let delegation_metadata = DelegationMetadata { + seeds: args.delegate.seeds, + last_update_nonce: 0, + is_undelegatable: false, + rent_payer: payer.address().to_bytes().into(), + }; + + // Initialize the delegation metadata PDA + create_pda( + delegation_metadata_account, + &crate::fast::ID, + delegation_metadata.serialized_size(), + &[Signer::from(&[ + Seed::from(pda::DELEGATION_METADATA_TAG), + Seed::from(delegated_account.address().as_ref()), + Seed::from(&[delegation_metadata_bump]), + ])], + payer, + )?; + + // Copy the seeds to the delegated metadata PDA + let mut delegation_metadata_data = + delegation_metadata_account.try_borrow_mut()?; + delegation_metadata + .to_bytes_with_discriminator(&mut delegation_metadata_data.as_mut()) + .map_err(to_pinocchio_program_error)?; + + // Copy the data from the buffer into the original account + if !delegate_buffer_account.is_data_empty() { + let mut delegated_data = delegated_account.try_borrow_mut()?; + let delegate_buffer_data = delegate_buffer_account.try_borrow()?; + (*delegated_data).copy_from_slice(&delegate_buffer_data); + } + + // Make the account rent exempt if it's not + if delegated_account.lamports() == 0 && delegated_account.data_len() == 0 { + system::Transfer { + from: payer, + to: delegated_account, + lamports: RENT_EXCEPTION_ZERO_BYTES_LAMPORTS, + } + .invoke()?; + } + + Ok(()) +} diff --git a/src/processor/fast/mod.rs b/src/processor/fast/mod.rs index 8413fdcb..2e975201 100644 --- a/src/processor/fast/mod.rs +++ b/src/processor/fast/mod.rs @@ -5,6 +5,7 @@ mod commit_finalize_from_buffer; mod commit_state; mod commit_state_from_buffer; mod delegate; +mod delegate_with_actions; mod finalize; mod undelegate; mod undelegate_confined_account; @@ -19,6 +20,7 @@ pub use commit_finalize_from_buffer::*; pub use commit_state::*; pub use commit_state_from_buffer::*; pub use delegate::*; +pub use delegate_with_actions::*; pub use finalize::*; pub use undelegate::*; pub use undelegate_confined_account::*; diff --git a/src/processor/fast/utils/requires.rs b/src/processor/fast/utils/requires.rs index 3bee15df..e7a88a0c 100644 --- a/src/processor/fast/utils/requires.rs +++ b/src/processor/fast/utils/requires.rs @@ -169,6 +169,34 @@ macro_rules! require_n_accounts { }}; } +#[macro_export] +macro_rules! require_n_accounts_with_optionals { + ( $accounts:expr, $n:literal) => {{ + match $accounts.len().cmp(&$n) { + core::cmp::Ordering::Less => { + pinocchio_log::log!( + "Need {} accounts, but got less ({}) accounts", + $n, + $accounts.len() + ); + return Err( + pinocchio::error::ProgramError::NotEnoughAccountKeys, + ); + } + _ => { + let (exact, optionals) = $accounts.split_at($n); + + ( + TryInto::<&[_; $n]>::try_into(exact).map_err(|_| { + $crate::error::DlpError::InfallibleError + })?, + optionals, + ) + } + } + }}; +} + #[macro_export] macro_rules! require_some { ($option:expr, $error:expr) => {{ diff --git a/src/state/utils/try_from_bytes.rs b/src/state/utils/try_from_bytes.rs index 964f22b0..f3060746 100644 --- a/src/state/utils/try_from_bytes.rs +++ b/src/state/utils/try_from_bytes.rs @@ -5,26 +5,28 @@ macro_rules! impl_try_from_bytes_with_discriminator_zero_copy { pub fn try_from_bytes_with_discriminator( data: &[u8], ) -> Result<&Self, ::solana_program::program_error::ProgramError> { - if data.len() < 8 { + let expected_len = 8 + ::std::mem::size_of::(); + if data.len() < expected_len { return Err($crate::error::DlpError::InvalidDataLength.into()); } if Self::discriminator().to_bytes().ne(&data[..8]) { return Err($crate::error::DlpError::InvalidDiscriminator.into()); } - bytemuck::try_from_bytes::(&data[8..]).or(Err( + bytemuck::try_from_bytes::(&data[8..expected_len]).or(Err( $crate::error::DlpError::InvalidDelegationRecordData.into(), )) } pub fn try_from_bytes_with_discriminator_mut( data: &mut [u8], ) -> Result<&mut Self, ::solana_program::program_error::ProgramError> { - if data.len() < 8 { + let expected_len = 8 + ::std::mem::size_of::(); + if data.len() < expected_len { return Err($crate::error::DlpError::InvalidDataLength.into()); } if Self::discriminator().to_bytes().ne(&data[..8]) { return Err($crate::error::DlpError::InvalidDiscriminator.into()); } - bytemuck::try_from_bytes_mut::(&mut data[8..]).or(Err( + bytemuck::try_from_bytes_mut::(&mut data[8..expected_len]).or(Err( $crate::error::DlpError::InvalidDelegationRecordData.into(), )) } diff --git a/tests/test_delegate_with_actions.rs b/tests/test_delegate_with_actions.rs new file mode 100644 index 00000000..e250430a --- /dev/null +++ b/tests/test_delegate_with_actions.rs @@ -0,0 +1,165 @@ +use dlp::{ + args::{DelegateArgs, DelegateWithActionsArgs, Instructions}, + compact, + instruction_builder::delegate_with_actions, +}; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, +}; + +#[test] +fn test_compact_account_meta_bit_packing() { + let packed = compact::AccountMeta::new_readonly(42, true); + assert_eq!(packed.index(), 42); + assert!(packed.is_signer()); + assert!(!packed.is_writable()); + + let packed = compact::AccountMeta::new(63, false); + assert_eq!(packed.index(), 63); + assert!(!packed.is_signer()); + assert!(packed.is_writable()); + + assert!(compact::AccountMeta::try_new(64, true, true).is_none()); +} + +#[test] +fn test_delegate_with_actions_bincode_roundtrip_compact_payload() { + let payer = Pubkey::new_unique(); + let signer = Pubkey::new_unique(); + + let instructions = vec![ + Instruction { + program_id: Pubkey::new_unique(), + accounts: vec![ + AccountMeta::new_readonly(payer, true), + AccountMeta::new(Pubkey::new_unique(), false), + ], + data: vec![1, 2, 3], + }, + Instruction { + program_id: Pubkey::new_unique(), + accounts: vec![AccountMeta::new_readonly(signer, true)], + data: vec![9, 9], + }, + ]; + + let ix = delegate_with_actions( + payer, + Pubkey::new_unique(), + Some(Pubkey::new_unique()), + DelegateArgs { + commit_frequency_ms: 500, + seeds: vec![b"seed-a".to_vec()], + validator: Some(Pubkey::new_unique()), + }, + instructions, + false, + ); + + let args: DelegateWithActionsArgs = + bincode::deserialize(&ix.data[8..]).unwrap(); + assert_eq!(args.delegate.commit_frequency_ms, 500); + assert_eq!(args.actions.signer_count, 2); + match args.actions.instructions { + Instructions::ClearText { instructions } => { + assert_eq!(instructions.len(), 2); + } + Instructions::Encrypted { .. } => { + panic!("expected cleartext compact instructions"); + } + } + assert!(args.actions.pubkeys.len() <= compact::MAX_PUBKEYS as usize); +} + +#[test] +fn test_delegate_with_actions_builder_adds_compact_signers_to_remaining_accounts( +) { + let payer = Pubkey::new_unique(); + let delegated_account = Pubkey::new_unique(); + let owner = Pubkey::new_unique(); + let signer_a = Pubkey::new_unique(); + let signer_b = Pubkey::new_unique(); + + let instructions = vec![ + Instruction { + program_id: Pubkey::new_unique(), + accounts: vec![ + AccountMeta::new_readonly(signer_a, true), + AccountMeta::new_readonly(signer_b, true), + ], + data: vec![7, 7], + }, + Instruction { + program_id: Pubkey::new_unique(), + accounts: vec![ + AccountMeta::new_readonly(signer_a, true), + AccountMeta::new(Pubkey::new_unique(), false), + ], + data: vec![8, 8], + }, + ]; + + let ix = delegate_with_actions( + payer, + delegated_account, + Some(owner), + DelegateArgs::default(), + instructions, + false, + ); + + // first 7 are the required delegate_with_actions accounts + let remaining = &ix.accounts[7..]; + assert_eq!(remaining.len(), 2); + assert!(remaining.iter().all(|a| a.is_signer && !a.is_writable)); + assert!(remaining.iter().any(|a| a.pubkey == signer_a)); + assert!(remaining.iter().any(|a| a.pubkey == signer_b)); +} + +#[test] +#[cfg(feature = "sdk")] +fn test_delegate_with_actions_builder_private_sets_encrypted_payload() { + use dlp::encryption; + use solana_sdk::signature::Keypair; + + let validator = Keypair::new(); + let validator_secret = encryption::keypair_to_x25519_secret(&validator); + + let payer = Pubkey::new_unique(); + let signer = Pubkey::new_unique(); + let instructions = vec![Instruction { + program_id: Pubkey::new_unique(), + accounts: vec![AccountMeta::new_readonly(signer, true)], + data: vec![4, 2], + }]; + + let ix = delegate_with_actions( + payer, + Pubkey::new_unique(), + Some(Pubkey::new_unique()), + DelegateArgs { + validator: Some(validator.pubkey()), + ..Default::default() + }, + instructions, + true, + ); + + let args: DelegateWithActionsArgs = + bincode::deserialize(&ix.data[8..]).unwrap(); + assert_eq!(args.actions.signer_count, 1); + match args.actions.instructions { + Instructions::Encrypted { instructions } => { + let decrypted = + encryption::decrypt(&instructions, &validator_secret).unwrap(); + let decoded: Vec = + bincode::deserialize(&decrypted).unwrap(); + assert_eq!(decoded.len(), 1); + assert_eq!(decoded[0].data, vec![4, 2]); + } + Instructions::ClearText { .. } => { + panic!("expected encrypted compact instructions"); + } + } +} From 8edbf208269788c6d747edab9793d920eea3d151 Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Thu, 26 Feb 2026 20:02:09 +0530 Subject: [PATCH 2/4] add new action format with selective encryption --- src/args/delegate_with_actions.rs | 59 ++- src/compact/account_meta.rs | 2 +- src/diff/algorithm.rs | 2 +- src/encryption/mod.rs | 18 +- .../delegate_with_actions.rs | 454 ++++++++++-------- src/lib.rs | 19 +- src/processor/fast/delegate_with_actions.rs | 62 ++- tests/test_delegate_with_actions.rs | 116 ++--- 8 files changed, 413 insertions(+), 319 deletions(-) diff --git a/src/args/delegate_with_actions.rs b/src/args/delegate_with_actions.rs index ae9aff41..b8380339 100644 --- a/src/args/delegate_with_actions.rs +++ b/src/args/delegate_with_actions.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use solana_program::{instruction::Instruction, pubkey::Pubkey}; +use solana_program::pubkey::Pubkey; use super::DelegateArgs; use crate::compact; @@ -15,34 +15,47 @@ pub struct DelegateWithActionsArgs { #[derive(Debug, Serialize, Deserialize)] pub struct PostDelegationActions { - /// Number of signer pubkeys in the `pubkeys` prefix. - /// First `signer_count` entries of `pubkeys` are required signers. - pub signer_count: u8, + pub signers: Vec, - /// Shared pubkey table. Account metas and program IDs reference this table by index. - pub pubkeys: Vec, + pub non_signers: Vec, - /// Instruction payload in compact cleartext or encrypted bytes. - pub instructions: Instructions, + pub instructions: Vec, } #[derive(Clone, Debug, Serialize, Deserialize)] -pub enum Instructions { - /// Compact cleartext instructions. - ClearText { - instructions: Vec, - }, - - /// Encrypted compact instruction bytes. - Encrypted { instructions: Vec }, +pub struct MaybeEncryptedInstruction { + pub program_id: u8, + + pub accounts: Vec, + + pub data: MaybeEncryptedIxData, } -#[derive(Debug, Serialize, Deserialize)] -pub struct DecryptedInstructions { - /// Sender-provided nonce/salt to randomize ciphertext so identical - /// plaintext does not always map to identical encrypted bytes. - pub random_salt: u64, +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum MaybeEncryptedPubkey { + ClearText(Pubkey), + Encrypted(EncryptedBuffer), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct MaybeEncryptedIxData { + pub prefix: Vec, + pub suffix: EncryptedBuffer, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct EncryptedBuffer(Vec); + +impl EncryptedBuffer { + pub fn new(bytes: Vec) -> Self { + Self(bytes) + } + + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } - /// Decrypted instructions ready for execution. - pub instructions: Vec, + pub fn into_inner(self) -> Vec { + self.0 + } } diff --git a/src/compact/account_meta.rs b/src/compact/account_meta.rs index fb828e45..c1c58e6e 100644 --- a/src/compact/account_meta.rs +++ b/src/compact/account_meta.rs @@ -41,7 +41,7 @@ impl AccountMeta { Some(Self(packed)) } - pub fn index(self) -> u8 { + pub fn key(self) -> u8 { self.0 & ACCOUNT_INDEX_MASK } diff --git a/src/diff/algorithm.rs b/src/diff/algorithm.rs index 7f51e0e7..ed34f44e 100644 --- a/src/diff/algorithm.rs +++ b/src/diff/algorithm.rs @@ -558,7 +558,7 @@ mod tests { // TODO (snawaz): unwritten == &mut [], is because currently the expanded bytes are part of the diff. // Once compute_diff is optimized further, written must be &mut [0; 20]. - assert_eq!(unwritten, &mut []); + assert_eq!(unwritten, &mut [] as &mut [u8]); destination }; diff --git a/src/encryption/mod.rs b/src/encryption/mod.rs index e06c4a55..4d7a3ad1 100644 --- a/src/encryption/mod.rs +++ b/src/encryption/mod.rs @@ -1,9 +1,13 @@ +#[cfg(feature = "sdk")] use curve25519_dalek::{ edwards::CompressedEdwardsY, montgomery::MontgomeryPoint, }; use serde::{Deserialize, Serialize}; -use sha2::{Digest, Sha512}; use solana_program::hash::hashv; + +#[cfg(feature = "sdk")] +use sha2::{Digest, Sha512}; +#[cfg(feature = "sdk")] use x25519_dalek::{PublicKey as X25519Public, StaticSecret as X25519Secret}; pub const KEY_LEN: usize = 32; @@ -24,6 +28,7 @@ pub struct EncryptedPayloadV1 { } /// Convert an Ed25519 public key into an X25519 public key. +#[cfg(feature = "sdk")] pub fn ed25519_pubkey_to_x25519( ed25519_pubkey: &[u8; KEY_LEN], ) -> Option<[u8; KEY_LEN]> { @@ -36,6 +41,7 @@ pub fn ed25519_pubkey_to_x25519( /// /// This follows the libsodium-style conversion: /// SHA-512(seed) then clamp the first 32 bytes. +#[cfg(feature = "sdk")] pub fn ed25519_secret_to_x25519( ed25519_secret_seed: &[u8; KEY_LEN], ) -> [u8; KEY_LEN] { @@ -89,6 +95,14 @@ pub fn encrypt_ed25519_recipient( Ok(encrypt(plaintext, &recipient_x25519_pubkey)) } +#[cfg(not(feature = "sdk"))] +pub fn encrypt_ed25519_recipient( + plaintext: &[u8], + recipient_ed25519_pubkey: &[u8; KEY_LEN], +) -> Result, EncryptionError> { + panic!("encrypt_ed25519_recipient requires `sdk` feature"); +} + /// Encrypt any plaintext bytes for a recipient X25519 public key. /// /// The caller supplies an ephemeral secret key (random per message). @@ -96,6 +110,7 @@ pub fn encrypt_ed25519_recipient( /// /// Example: /// `let encrypted = encrypt_with_ephemeral(b"hello", &recipient_pubkey, &ephemeral_secret);` +#[cfg(feature = "sdk")] pub fn encrypt_with_ephemeral( plaintext: &[u8], recipient_x25519_pubkey: &[u8; KEY_LEN], @@ -120,6 +135,7 @@ pub fn encrypt_with_ephemeral( /// /// Example: /// `let plaintext = decrypt(&encrypted, &recipient_x25519_secret)?;` +#[cfg(feature = "sdk")] pub fn decrypt( encrypted_payload: &[u8], recipient_x25519_secret: &[u8; KEY_LEN], diff --git a/src/instruction_builder/delegate_with_actions.rs b/src/instruction_builder/delegate_with_actions.rs index 4aeec111..9260195f 100644 --- a/src/instruction_builder/delegate_with_actions.rs +++ b/src/instruction_builder/delegate_with_actions.rs @@ -6,10 +6,10 @@ use solana_program::{ use crate::{ args::{ - DelegateArgs, DelegateWithActionsArgs, Instructions, - PostDelegationActions, + DelegateArgs, DelegateWithActionsArgs, EncryptedBuffer, + MaybeEncryptedIxData, MaybeEncryptedPubkey, PostDelegationActions, }, - compact::{self}, + compact, discriminator::DlpDiscriminator, pda::{ delegate_buffer_pda_from_delegated_account_and_owner_program, @@ -18,18 +18,116 @@ use crate::{ }, }; -/// Builds a delegate instruction that stores an actions payload. +pub trait Encryptable: Sized { + type Output; + fn encrypted(self) -> Self::Output { + self.with_encryption(true) + } + fn cleartext(self) -> Self::Output { + self.with_encryption(false) + } + fn with_encryption(self, encrypt: bool) -> Self::Output; +} + +pub struct PostDelegationInstruction { + pub program_id: EncryptablePubkey, + pub accounts: Vec, + pub data: EncryptableIxData, +} + +#[derive(Clone, Debug)] +pub struct EncryptableIxData { + pub data: Vec, + + /// [0, encrypt_offset) is cleartext and [encrypt_offset, len) is encrypted + pub encrypt_begin_offset: usize, +} + +impl EncryptableIxData { + fn encrypt(self, validator: &Option) -> MaybeEncryptedIxData { + if self.encrypt_begin_offset >= self.data.len() { + MaybeEncryptedIxData { + prefix: self.data, + suffix: EncryptedBuffer::default(), + } + } else { + let validator = validator.expect(""); + MaybeEncryptedIxData { + prefix: self.data[0..self.encrypt_begin_offset].into(), + // TODO (snawaz): finish it + suffix: { + EncryptedBuffer::new( + crate::encryption::encrypt_ed25519_recipient( + &self.data[self.encrypt_begin_offset..], + validator.as_array(), + ) + .expect(""), + ) + }, + } + } + } +} + +#[derive(Clone, Debug)] +pub struct EncryptablePubkey { + pub pubkey: Pubkey, + pub is_encryptable: bool, +} + +impl Encryptable for Pubkey { + type Output = EncryptablePubkey; + fn with_encryption(self, encrypt: bool) -> Self::Output { + EncryptablePubkey { + pubkey: self, + is_encryptable: encrypt, + } + } +} + +#[derive(Clone, Debug)] +pub struct EncryptableAccountMeta { + pub account_meta: AccountMeta, + pub is_encryptable: bool, +} + +impl EncryptableAccountMeta { + fn encrypt(self, validator: &Option) -> MaybeEncryptedPubkey { + if self.is_encryptable { + let validator = validator.expect(""); + MaybeEncryptedPubkey::Encrypted(EncryptedBuffer::new( + crate::encryption::encrypt_ed25519_recipient( + self.account_meta.pubkey.as_array(), + validator.as_array(), + ) + .expect(""), + )) + } else { + MaybeEncryptedPubkey::ClearText(self.account_meta.pubkey) + } + } +} + +impl Encryptable for AccountMeta { + type Output = EncryptableAccountMeta; + fn with_encryption(self, encrypt: bool) -> Self::Output { + EncryptableAccountMeta { + account_meta: self, + is_encryptable: encrypt, + } + } +} + /// See [crate::processor::process_delegate_with_actions] for docs. pub fn delegate_with_actions( payer: Pubkey, delegated_account: Pubkey, owner: Option, delegate: DelegateArgs, - actions: Vec, - private: bool, + actions: Vec, ) -> Instruction { - let actions = - compact_post_delegation_actions(actions, private, delegate.validator); + let (actions, signers) = + compact_post_delegation_actions(actions, delegate.validator); Instruction { program_id: crate::id(), @@ -60,12 +158,7 @@ pub fn delegate_with_actions( AccountMeta::new(delegation_metadata_pda, false), AccountMeta::new_readonly(system_program::id(), false), ], - actions - .pubkeys - .iter() - .take(actions.signer_count as usize) - .map(|signer| AccountMeta::new_readonly(*signer, true)) - .collect(), + signers, ] .concat() }, @@ -80,178 +173,137 @@ pub fn delegate_with_actions( } fn compact_post_delegation_actions( - instructions: Vec, - private: bool, + instructions: Vec, validator: Option, -) -> PostDelegationActions { - let mut pubkeys: Vec<(Pubkey, usize, bool)> = Vec::new(); // Vec of (pubkey, index, signer) +) -> (PostDelegationActions, Vec) { + use crate::args::MaybeEncryptedInstruction; + let mut signers: Vec = Vec::new(); + + let mut add_to_signers = |meta: &EncryptableAccountMeta| { + assert!(meta.account_meta.is_signer, "AccountMeta must be a signer"); + assert!(!meta.is_encryptable, "signer must not be encryptable"); + let Some(found) = signers + .iter_mut() + .find(|m| m.pubkey == meta.account_meta.pubkey) + else { + signers.push(meta.account_meta.clone()); + return; + }; - // return index to pubkeys - let mut index_of = |key: Pubkey, signer: bool| -> u8 { - if let Some(index) = - pubkeys.iter().position(|(existing, _, _)| *existing == key) - { - pubkeys[index].2 |= signer; - return index as u8; - } - assert!( - pubkeys.len() < compact::MAX_PUBKEYS as usize, - "delegate_with_actions supports at most {} unique pubkeys", - compact::MAX_PUBKEYS - ); - pubkeys.push((key, pubkeys.len(), signer)); - pubkeys.len() as u8 - 1 + found.is_signer |= meta.account_meta.is_signer; + found.is_writable |= meta.account_meta.is_writable; }; - let compact_instructions = instructions - .into_iter() - .map(|ix| compact::Instruction::from_instruction(ix, &mut index_of)) - .collect(); - - let (pubkeys, compact_instructions, signer_count) = - reorder_signers_first(pubkeys, compact_instructions); + let mut non_signers: Vec = Vec::new(); + let mut add_to_non_signers = |meta: &EncryptableAccountMeta| { + assert!( + !meta.account_meta.is_signer, + "AccountMeta must not be a signer" + ); + let Some(found) = non_signers + .iter_mut() + .find(|m| m.account_meta.pubkey == meta.account_meta.pubkey) + else { + non_signers.push(meta.clone()); + return; + }; - let compact_payload = if private { - let serialized = bincode::serialize(&compact_instructions) - .expect("compact instruction serialization should not fail"); - let validator = validator - .expect("delegate.validator is required when private is true"); + found.is_encryptable |= meta.is_encryptable; + found.account_meta.is_writable |= meta.account_meta.is_writable; + }; - #[cfg(feature = "sdk")] - { - Instructions::Encrypted { - instructions: crate::encryption::encrypt_ed25519_recipient( - &serialized, - &validator.to_bytes(), - ) - .expect("validator ed25519 pubkey must convert to x25519"), - } - } + for meta in instructions + .iter() + .flat_map(|ix| ix.accounts.iter()) + .filter(|meta| meta.account_meta.is_signer) + { + add_to_signers(meta); + } - #[cfg(not(feature = "sdk"))] + for ix in instructions.iter() { + add_to_non_signers(&EncryptableAccountMeta { + account_meta: AccountMeta::new_readonly( + ix.program_id.pubkey, + false, + ), + is_encryptable: ix.program_id.is_encryptable, + }); + for meta in ix + .accounts + .iter() + .filter(|meta| !meta.account_meta.is_signer) { - let _ = (serialized, validator); - panic!("private delegate_with_actions requires sdk feature"); + let Some(found) = signers + .iter_mut() + .find(|m| m.pubkey == meta.account_meta.pubkey) + else { + add_to_non_signers(meta); + continue; + }; + + found.is_writable |= meta.account_meta.is_writable; } - } else { - Instructions::ClearText { - instructions: compact_instructions.clone(), - } - }; - - PostDelegationActions { - signer_count, - pubkeys, - instructions: compact_payload, } -} -fn reorder_signers_first( - mut pubkeys: Vec<(Pubkey, usize, bool)>, - mut instructions: Vec, -) -> (Vec, Vec, u8) { - if pubkeys.is_empty() { - return (Vec::new(), instructions, 0); + if signers.len() + non_signers.len() > compact::MAX_PUBKEYS as usize { + panic!( + "delegate_with_actions supports at most {} unique pubkeys", + compact::MAX_PUBKEYS + ); } - let signer_count = partition(&mut pubkeys, |(_, _, signer)| *signer); - - let new_index = |old_index: u8| -> u8 { - pubkeys - .iter() - .position(|(_, index, _)| *index == old_index as usize) - .unwrap() as u8 + let index_of = |pk: &Pubkey| -> u8 { + if let Some(index) = signers.iter().position(|s| &s.pubkey == pk) { + return index as u8; + } + signers.len() as u8 + + non_signers + .iter() + .position(|ns| &ns.account_meta.pubkey == pk) + .unwrap() as u8 }; - for ix in instructions.iter_mut() { - ix.program_id = new_index(ix.program_id); - for meta in ix.accounts.iter_mut() { - meta.set_index(new_index(meta.index())); - } - } + let compact_instructions: Vec = instructions + .into_iter() + .map(|ix| MaybeEncryptedInstruction { + program_id: index_of(&ix.program_id.pubkey), + + accounts: ix + .accounts + .into_iter() + .map(|meta| { + compact::AccountMeta::try_new( + index_of(&meta.account_meta.pubkey), + meta.account_meta.is_signer, + meta.account_meta.is_writable, + ) + .expect("compact account index must fit in 6 bits") + }) + .collect(), + + data: ix.data.encrypt(&validator), + }) + .collect(); - let pubkeys = pubkeys.into_iter().map(|(key, _, _)| key).collect(); - (pubkeys, instructions, signer_count as u8) -} + ( + PostDelegationActions { + signers: signers.iter().map(|s| s.pubkey).collect(), -/// -/// It's a C++ equivalent of std::partition() -/// ref: https://en.cppreference.com/w/cpp/algorithm/partition.html -/// -/// Returns the size of first group (good elements) which can also be used as the -/// index of the first element in the second group. -/// -fn partition(v: &mut [T], mut pred: F) -> usize -where - F: FnMut(&T) -> bool, -{ - let mut good = 0; // number of good elements - - for i in 0..v.len() { - if pred(&v[i]) { - v.swap(good, i); - good += 1; - } - } + non_signers: non_signers + .into_iter() + .map(|ns| ns.encrypt(&validator)) + .collect(), - good + instructions: compact_instructions, + }, + signers, + ) } #[cfg(test)] mod tests { use super::*; - #[test] - fn test_reorder_signers_first_remaps_and_prefixes_signers() { - let a = Pubkey::new_from_array([1; 32]); // 0: signer - let b = Pubkey::new_from_array([2; 32]); // 1: non-signer - let c = Pubkey::new_from_array([3; 32]); // 2: signer - let d = Pubkey::new_from_array([4; 32]); // 3: non-signer - let e = Pubkey::new_from_array([5; 32]); // 4: signer - - // (pubkey, old_index, is_signer) - let pubkeys = vec![ - (a, 0, true), - (b, 1, false), - (c, 2, true), - (d, 3, false), - (e, 4, true), - ]; - let instructions = vec![compact::Instruction { - program_id: 3, // old index of d - accounts: vec![ - compact::AccountMeta::new_readonly(0, true), // a - compact::AccountMeta::new(2, true), // c - compact::AccountMeta::new_readonly(1, false), // b - compact::AccountMeta::new_readonly(4, true), // e - compact::AccountMeta::new(3, false), // d - ], - data: vec![9], - }]; - - let (reordered_pubkeys, ixs, signer_count) = - reorder_signers_first(pubkeys, instructions); - - // reordered: a, c, e, d, b - // 0, 1, 2, 3, 4 - - assert_eq!(signer_count, 3); - assert_eq!(reordered_pubkeys[0], a); // signer - assert_eq!(reordered_pubkeys[1], c); // signer - assert_eq!(reordered_pubkeys[2], e); // signer - assert_eq!(reordered_pubkeys[3], d); // non-signer - assert_eq!(reordered_pubkeys[4], b); // non-signer - - // old->new mapping: a(0)->0, b(1)->4, c(2)->1, d(3)->3, e(4)->2 - // - assert_eq!(ixs[0].program_id, 3); // d - assert_eq!(ixs[0].accounts[0].index(), 0); // a - assert_eq!(ixs[0].accounts[1].index(), 1); // c - assert_eq!(ixs[0].accounts[2].index(), 4); // b - assert_eq!(ixs[0].accounts[3].index(), 2); // e - assert_eq!(ixs[0].accounts[4].index(), 3); // d - } - #[test] fn test_compact_post_delegation_actions() { let a = Pubkey::new_from_array([1; 32]); // 0: signer @@ -260,43 +312,59 @@ mod tests { let d = Pubkey::new_from_array([4; 32]); // 3: non-signer let e = Pubkey::new_from_array([5; 32]); // 4: signer - let instructions = vec![Instruction { - program_id: d, + let instructions = vec![PostDelegationInstruction { + program_id: d.encrypted(), accounts: vec![ - AccountMeta::new_readonly(a, true), // a - AccountMeta::new(c, true), // c - AccountMeta::new_readonly(b, false), // b - AccountMeta::new_readonly(e, true), // e - AccountMeta::new(d, false), // d + AccountMeta::new_readonly(a, true).cleartext(), // a + AccountMeta::new(c, true).cleartext(), // c + AccountMeta::new_readonly(b, false).encrypted(), // b + AccountMeta::new_readonly(e, true).cleartext(), // e + AccountMeta::new(d, false).encrypted(), // d ], - data: vec![9], + data: EncryptableIxData { + data: vec![9], + encrypt_begin_offset: 1, + }, }]; - let actions = - compact_post_delegation_actions(instructions, false, None); - - // reordered: a, c, e, d, b - // 0, 1, 2, 3, 4 + let (actions, _meta_signers) = compact_post_delegation_actions( + instructions, + Some(Pubkey::new_unique()), + ); - assert_eq!(actions.signer_count, 3); - assert_eq!(actions.pubkeys[0], a); // signer - assert_eq!(actions.pubkeys[1], c); // signer - assert_eq!(actions.pubkeys[2], e); // signer - assert_eq!(actions.pubkeys[3], b); // non-signer - assert_eq!(actions.pubkeys[4], d); // non-signer + // indices: a, c, e, d, b + // 0, 1, 2, 3, 4 + + assert_eq!(actions.signers.len(), 3); + assert_eq!(actions.signers[0], a); // signer + assert_eq!(actions.signers[1], c); // signer + assert_eq!(actions.signers[2], e); // signer + + if false { + let non_signer_pubkeys: Vec<_> = actions + .non_signers + .iter() + .map(|key| match key { + MaybeEncryptedPubkey::ClearText(pubkey) => *pubkey, + MaybeEncryptedPubkey::Encrypted(buffer) => { + panic!("there must not be any encrypted pubkeys") + // assert!(!buffer.as_bytes().is_empty()) + } + }) + .collect(); + + assert_eq!(non_signer_pubkeys[0], d); // non-signer + assert_eq!(non_signer_pubkeys[1], b); // non-signer + } else { + assert_eq!(actions.non_signers.len(), 2); // non-signer + } // old->new mapping: a(0)->0, b(1)->4, c(2)->1, d(3)->3, e(4)->2 - let Instructions::ClearText { instructions: ixs } = - actions.instructions - else { - panic!(); - }; - - assert_eq!(ixs[0].program_id, 4); // d - assert_eq!(ixs[0].accounts[0].index(), 0); // a - assert_eq!(ixs[0].accounts[1].index(), 1); // c - assert_eq!(ixs[0].accounts[2].index(), 3); // b - assert_eq!(ixs[0].accounts[3].index(), 2); // e - assert_eq!(ixs[0].accounts[4].index(), 4); // d + assert_eq!(actions.instructions[0].program_id, 3); // d + assert_eq!(actions.instructions[0].accounts[0].key(), 0); // a + assert_eq!(actions.instructions[0].accounts[1].key(), 1); // c + assert_eq!(actions.instructions[0].accounts[2].key(), 4); // b + assert_eq!(actions.instructions[0].accounts[3].key(), 2); // e + assert_eq!(actions.instructions[0].accounts[4].key(), 3); // d } } diff --git a/src/lib.rs b/src/lib.rs index 7a061a7f..f3905958 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,15 +1,15 @@ #![allow(unexpected_cfgs)] // Exactly one of `sdk` or `program` must be enabled -#[cfg(all(feature = "sdk", feature = "program"))] -compile_error!( - "Features `sdk` and `program` are mutually exclusive. Enable exactly one." -); - -#[cfg(all(not(feature = "sdk"), not(feature = "program")))] -compile_error!( - "Enable either `program` (default) or `sdk`. Building with neither is not supported." -); +//#[cfg(all(feature = "sdk", feature = "program"))] +//compile_error!( +// "Features `sdk` and `program` are mutually exclusive. Enable exactly one." +//); +// +//#[cfg(all(not(feature = "sdk"), not(feature = "program")))] +//compile_error!( +// "Enable either `program` (default) or `sdk`. Building with neither is not supported." +//); use solana_program::declare_id; @@ -37,7 +37,6 @@ pub mod state; mod account_size_class; -#[cfg(feature = "sdk")] pub mod encryption; pub use account_size_class::*; diff --git a/src/processor/fast/delegate_with_actions.rs b/src/processor/fast/delegate_with_actions.rs index 4598f917..38f11b13 100644 --- a/src/processor/fast/delegate_with_actions.rs +++ b/src/processor/fast/delegate_with_actions.rs @@ -9,7 +9,7 @@ use pinocchio_log::log; use pinocchio_system::instructions as system; use crate::{ - args::{DelegateWithActionsArgs, Instructions}, + args::DelegateWithActionsArgs, consts::{DEFAULT_VALIDATOR_IDENTITY, RENT_EXCEPTION_ZERO_BYTES_LAMPORTS}, error::DlpError, pda, @@ -27,7 +27,7 @@ use crate::{ }, utils::curve::is_on_curve_fast, }, - require_n_accounts_with_optionals, + require, require_n_accounts_with_optionals, state::{DelegationMetadata, DelegationRecord}, }; @@ -96,44 +96,40 @@ pub fn process_delegate_with_actions( DelegationMetadataCtx, )?; - // Validate instruction payload shape up-front. This confirms delegate args - // and actions envelope format, while encrypted bytes remain opaque. let args: DelegateWithActionsArgs = bincode::deserialize(data) .map_err(|_| ProgramError::InvalidInstructionData)?; - if args.actions.signer_count as usize > args.actions.pubkeys.len() { - return Err(ProgramError::InvalidInstructionData); - } - - // Validate compact indices for cleartext payload. - if let Instructions::ClearText { instructions } = &args.actions.instructions + // Validate instruction payload shape up-front. This confirms delegate args + // and actions envelope format, while encrypted bytes remain opaque. { - for compact_ix in instructions { - if compact_ix.program_id as usize >= args.actions.pubkeys.len() { - return Err(ProgramError::InvalidInstructionData); - } - for compact_meta in &compact_ix.accounts { - let pubkey_index = compact_meta.index(); - if pubkey_index as usize >= args.actions.pubkeys.len() { - return Err(ProgramError::InvalidInstructionData); - } - if compact_meta.is_signer() - && pubkey_index >= args.actions.signer_count - { - return Err(ProgramError::InvalidInstructionData); - } + let signers_count = args.actions.signers.len() as u8; + let keys_count = signers_count + args.actions.non_signers.len() as u8; + + for ix in args.actions.instructions.iter() { + require!( + ix.program_id < keys_count, + ProgramError::InvalidInstructionData + ); + for account in &ix.accounts { + require!( + (account.is_signer() && account.key() < signers_count) + || account.key() < keys_count, + ProgramError::InvalidInstructionData + ); } } - } - // Enforce required signers from the pubkey-table prefix. - for signer in &args.actions.pubkeys[..args.actions.signer_count as usize] { - let account = remaining_accounts - .iter() - .find(|account| account.address().to_bytes() == signer.to_bytes()) - .ok_or(ProgramError::NotEnoughAccountKeys)?; - if !account.is_signer() { - return Err(ProgramError::MissingRequiredSignature); + // Enforce required signers from the pubkey-table prefix. + for signer in &args.actions.signers { + let account = remaining_accounts + .iter() + .find(|account| { + account.address().to_bytes() == signer.to_bytes() + }) + .ok_or(ProgramError::NotEnoughAccountKeys)?; + if !account.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } } } diff --git a/tests/test_delegate_with_actions.rs b/tests/test_delegate_with_actions.rs index e250430a..5cf2049b 100644 --- a/tests/test_delegate_with_actions.rs +++ b/tests/test_delegate_with_actions.rs @@ -1,12 +1,13 @@ use dlp::{ - args::{DelegateArgs, DelegateWithActionsArgs, Instructions}, + args::{DelegateArgs, DelegateWithActionsArgs}, compact, - instruction_builder::delegate_with_actions, -}; -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, + instruction_builder::{ + delegate_with_actions, Encryptable, EncryptableIxData, + EncryptablePubkey, PostDelegationInstruction, + }, }; +use solana_program::{instruction::AccountMeta, pubkey::Pubkey}; +use solana_sdk::signer::Signer; #[test] fn test_compact_account_meta_bit_packing() { @@ -29,18 +30,24 @@ fn test_delegate_with_actions_bincode_roundtrip_compact_payload() { let signer = Pubkey::new_unique(); let instructions = vec![ - Instruction { - program_id: Pubkey::new_unique(), + PostDelegationInstruction { + program_id: Pubkey::new_unique().cleartext(), accounts: vec![ - AccountMeta::new_readonly(payer, true), - AccountMeta::new(Pubkey::new_unique(), false), + AccountMeta::new_readonly(payer, true).cleartext(), + AccountMeta::new(Pubkey::new_unique(), false).cleartext(), ], - data: vec![1, 2, 3], + data: EncryptableIxData { + data: vec![1, 2, 3], + encrypt_offset: 3, + }, }, - Instruction { - program_id: Pubkey::new_unique(), - accounts: vec![AccountMeta::new_readonly(signer, true)], - data: vec![9, 9], + PostDelegationInstruction { + program_id: Pubkey::new_unique().cleartext(), + accounts: vec![AccountMeta::new_readonly(signer, true).cleartext()], + data: EncryptableIxData { + data: vec![9, 9], + encrypt_offset: 2, + }, }, ]; @@ -54,22 +61,17 @@ fn test_delegate_with_actions_bincode_roundtrip_compact_payload() { validator: Some(Pubkey::new_unique()), }, instructions, - false, ); let args: DelegateWithActionsArgs = bincode::deserialize(&ix.data[8..]).unwrap(); assert_eq!(args.delegate.commit_frequency_ms, 500); - assert_eq!(args.actions.signer_count, 2); - match args.actions.instructions { - Instructions::ClearText { instructions } => { - assert_eq!(instructions.len(), 2); - } - Instructions::Encrypted { .. } => { - panic!("expected cleartext compact instructions"); - } - } - assert!(args.actions.pubkeys.len() <= compact::MAX_PUBKEYS as usize); + assert_eq!(args.actions.signers.len(), 2); + assert_eq!(args.actions.instructions.len(), 2); + assert!( + args.actions.signers.len() + args.actions.non_signers.len() + <= compact::MAX_PUBKEYS as usize + ); } #[test] @@ -82,21 +84,27 @@ fn test_delegate_with_actions_builder_adds_compact_signers_to_remaining_accounts let signer_b = Pubkey::new_unique(); let instructions = vec![ - Instruction { - program_id: Pubkey::new_unique(), + PostDelegationInstruction { + program_id: Pubkey::new_unique().cleartext(), accounts: vec![ - AccountMeta::new_readonly(signer_a, true), - AccountMeta::new_readonly(signer_b, true), + AccountMeta::new_readonly(signer_a, true).cleartext(), + AccountMeta::new_readonly(signer_b, true).cleartext(), ], - data: vec![7, 7], + data: EncryptableIxData { + data: vec![7, 7], + encrypt_offset: 2, + }, }, - Instruction { - program_id: Pubkey::new_unique(), + PostDelegationInstruction { + program_id: Pubkey::new_unique().cleartext(), accounts: vec![ - AccountMeta::new_readonly(signer_a, true), - AccountMeta::new(Pubkey::new_unique(), false), + AccountMeta::new_readonly(signer_a, true).cleartext(), + AccountMeta::new(Pubkey::new_unique(), false).cleartext(), ], - data: vec![8, 8], + data: EncryptableIxData { + data: vec![8, 8], + encrypt_offset: 2, + }, }, ]; @@ -106,7 +114,6 @@ fn test_delegate_with_actions_builder_adds_compact_signers_to_remaining_accounts Some(owner), DelegateArgs::default(), instructions, - false, ); // first 7 are the required delegate_with_actions accounts @@ -118,7 +125,7 @@ fn test_delegate_with_actions_builder_adds_compact_signers_to_remaining_accounts } #[test] -#[cfg(feature = "sdk")] +//#[cfg(feature = "sdk")] fn test_delegate_with_actions_builder_private_sets_encrypted_payload() { use dlp::encryption; use solana_sdk::signature::Keypair; @@ -128,10 +135,13 @@ fn test_delegate_with_actions_builder_private_sets_encrypted_payload() { let payer = Pubkey::new_unique(); let signer = Pubkey::new_unique(); - let instructions = vec![Instruction { - program_id: Pubkey::new_unique(), - accounts: vec![AccountMeta::new_readonly(signer, true)], - data: vec![4, 2], + let instructions = vec![PostDelegationInstruction { + program_id: Pubkey::new_unique().cleartext(), + accounts: vec![AccountMeta::new_readonly(signer, true).cleartext()], + data: EncryptableIxData { + data: vec![4, 2], + encrypt_offset: 1, + }, }]; let ix = delegate_with_actions( @@ -143,23 +153,15 @@ fn test_delegate_with_actions_builder_private_sets_encrypted_payload() { ..Default::default() }, instructions, - true, ); let args: DelegateWithActionsArgs = bincode::deserialize(&ix.data[8..]).unwrap(); - assert_eq!(args.actions.signer_count, 1); - match args.actions.instructions { - Instructions::Encrypted { instructions } => { - let decrypted = - encryption::decrypt(&instructions, &validator_secret).unwrap(); - let decoded: Vec = - bincode::deserialize(&decrypted).unwrap(); - assert_eq!(decoded.len(), 1); - assert_eq!(decoded[0].data, vec![4, 2]); - } - Instructions::ClearText { .. } => { - panic!("expected encrypted compact instructions"); - } - } + assert_eq!(args.actions.signers.len(), 1); + let ix = &args.actions.instructions[0]; + assert_eq!(ix.data.prefix, vec![4]); + let decrypted = + encryption::decrypt(ix.data.suffix.as_bytes(), &validator_secret) + .unwrap(); + assert_eq!(decrypted, vec![2]); } From 2346f9c64bec36d28791a5a1d463f580b4a729d4 Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Sat, 28 Feb 2026 14:07:00 +0530 Subject: [PATCH 3/4] switch to libsodium-rs --- Cargo.lock | 314 +++++++++++++++--- Cargo.toml | 10 +- src/encryption/mod.rs | 283 +++++++--------- .../delegate_with_actions.rs | 189 +++++------ tests/test_call_handler.rs | 8 +- tests/test_delegate_with_actions.rs | 34 +- 6 files changed, 525 insertions(+), 313 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2e9c235f..dabb6586 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + [[package]] name = "aead" version = "0.5.2" @@ -117,6 +129,12 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -412,7 +430,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.7.4", "object", "rustc-demangle", ] @@ -618,9 +636,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bv" @@ -719,10 +737,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.6" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d6dbb628b8f8555f86d0323c2eb39e3ec81901f4b83e091db8a6a76d316a333" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -888,6 +907,15 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + [[package]] name = "cpufeatures" version = "0.2.12" @@ -899,9 +927,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -967,6 +995,28 @@ dependencies = [ "subtle", ] +[[package]] +name = "ct-codecs" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b10589d1a5e400d61f9f38f12f884cfd080ff345de8f17efda36fe0e4a02aa8" + +[[package]] +name = "ctor" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "424e0138278faeb2b401f174ad17e715c829512d74f3d1e81eb43365c2e0590e" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" + [[package]] name = "ctr" version = "0.9.2" @@ -1053,6 +1103,12 @@ dependencies = [ "syn 2.0.93", ] +[[package]] +name = "dary_heap" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06d2e3287df1c007e74221c49ca10a95d557349e54b3a75dc2fb14712c751f04" + [[package]] name = "dashmap" version = "5.5.3" @@ -1188,6 +1244,21 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" +[[package]] +name = "dtor" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "404d02eeb088a82cfd873006cb713fe411306c7d182c344905e101fb1167d301" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" + [[package]] name = "eager" version = "0.1.0" @@ -1381,6 +1452,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "five8" version = "1.0.0" @@ -1416,12 +1493,13 @@ checksum = "94474d15a76982be62ca8a39570dccce148d98c238ebb7408b0a21b2c4bdddc4" [[package]] name = "flate2" -version = "1.0.31" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.8.9", + "zlib-rs", ] [[package]] @@ -1439,6 +1517,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "foreign-types" version = "0.3.2" @@ -1665,7 +1749,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.12", "indexmap", "slab", "tokio", @@ -1712,6 +1796,17 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "heck" version = "0.4.1" @@ -1786,6 +1881,16 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + [[package]] name = "http-body" version = "0.4.6" @@ -1793,7 +1898,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.12", "pin-project-lite", ] @@ -1826,7 +1931,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", + "http 0.2.12", "http-body", "httparse", "httpdate", @@ -1846,7 +1951,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", - "http", + "http 0.2.12", "hyper", "rustls 0.21.12", "tokio", @@ -2205,6 +2310,30 @@ version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +[[package]] +name = "libflate" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3248b8d211bd23a104a42d81b4fa8bb8ac4a3b75e7a43d85d2c9ccb6179cd74" +dependencies = [ + "adler32", + "core2", + "crc32fast", + "dary_heap", + "libflate_lz77", +] + +[[package]] +name = "libflate_lz77" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a599cb10a9cd92b1300debcef28da8f70b935ec937f44fcd1b70a7c986a11c5c" +dependencies = [ + "core2", + "hashbrown 0.16.1", + "rle-decode-fast", +] + [[package]] name = "libredox" version = "0.1.3" @@ -2264,6 +2393,38 @@ dependencies = [ "libsecp256k1-core", ] +[[package]] +name = "libsodium-rs" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a6a6c03c914ffa9e53f76300ab4e81a86230f8b884c7f224eb6617ab685878" +dependencies = [ + "ct-codecs", + "ctor", + "libc", + "libsodium-sys-stable", + "pkg-config", + "thiserror 1.0.69", + "zeroize", +] + +[[package]] +name = "libsodium-sys-stable" +version = "1.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5d23f4a051a13cf1085b2c5a050d4d890d80c754534cc4247eff525fa5283d" +dependencies = [ + "cc", + "libc", + "libflate", + "minisign-verify", + "pkg-config", + "tar", + "ureq", + "vcpkg", + "zip", +] + [[package]] name = "light-poseidon" version = "0.2.0" @@ -2300,9 +2461,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.25" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lz4" @@ -2332,7 +2493,7 @@ dependencies = [ "borsh 1.5.7", "bytemuck", "const-crypto", - "curve25519-dalek 4.1.3", + "libsodium-rs", "magicblock-delegation-program", "num_enum", "pinocchio 0.10.1", @@ -2344,7 +2505,6 @@ dependencies = [ "rand 0.8.5", "rkyv", "serde", - "sha2 0.10.8", "solana-address", "solana-curve25519", "solana-program", @@ -2356,7 +2516,6 @@ dependencies = [ "strum 0.27.2", "thiserror 2.0.11", "tokio", - "x25519-dalek", ] [[package]] @@ -2417,6 +2576,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "minisign-verify" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e856fdd13623a2f5f2f54676a4ee49502a96a80ef4a62bcedd23d52427c44d43" + [[package]] name = "miniz_oxide" version = "0.7.4" @@ -2426,6 +2591,16 @@ dependencies = [ "adler", ] +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.0.1" @@ -2946,9 +3121,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "polyval" @@ -3353,7 +3528,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", + "http 0.2.12", "http-body", "hyper", "hyper-rustls", @@ -3392,7 +3567,7 @@ checksum = "5a735987236a8e238bf0296c7e351b999c188ccc11477f311b82b55c93984216" dependencies = [ "anyhow", "async-trait", - "http", + "http 0.2.12", "reqwest", "serde", "task-local-extensions", @@ -3443,6 +3618,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "rle-decode-fast" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -3867,6 +4048,12 @@ version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + [[package]] name = "simdutf8" version = "0.1.5" @@ -7058,9 +7245,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tar" -version = "0.4.43" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c65998313f8e17d0d553d28f91a0df93e4dbbbf770279c7bc21ca0f09ea1a1f6" +checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" dependencies = [ "filetime", "libc", @@ -7459,7 +7646,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http", + "http 0.2.12", "httparse", "log", "rand 0.8.5", @@ -7471,6 +7658,12 @@ dependencies = [ "webpki-roots 0.24.0", ] +[[package]] +name = "typed-path" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" + [[package]] name = "typenum" version = "1.17.0" @@ -7532,6 +7725,31 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc97a28575b85cfedf2a7e7d3cc64b3e11bd8ac766666318003abbacc7a21fc" +dependencies = [ + "base64 0.22.1", + "log", + "percent-encoding", + "ureq-proto", + "utf-8", +] + +[[package]] +name = "ureq-proto" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f" +dependencies = [ + "base64 0.22.1", + "http 1.4.0", + "httparse", + "log", +] + [[package]] name = "uriparse" version = "0.6.4" @@ -7996,18 +8214,6 @@ dependencies = [ "tap", ] -[[package]] -name = "x25519-dalek" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" -dependencies = [ - "curve25519-dalek 4.1.3", - "rand_core 0.6.4", - "serde", - "zeroize", -] - [[package]] name = "x509-parser" version = "0.14.0" @@ -8145,6 +8351,38 @@ dependencies = [ "syn 2.0.93", ] +[[package]] +name = "zip" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c42e33efc22a0650c311c2ef19115ce232583abbe80850bc8b66509ebef02de0" +dependencies = [ + "crc32fast", + "flate2", + "indexmap", + "memchr", + "typed-path", + "zopfli", +] + +[[package]] +name = "zlib-rs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c745c48e1007337ed136dc99df34128b9faa6ed542d80a1c673cf55a6d7236c8" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + [[package]] name = "zstd" version = "0.13.2" diff --git a/Cargo.toml b/Cargo.toml index 226cd688..6ccaf9b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,9 +21,7 @@ sdk = [ "no-entrypoint", "dep:solana-sdk", "dep:rand", - "dep:sha2", - "dep:x25519-dalek", - "dep:curve25519-dalek", + "dep:libsodium-rs", "dep:pinocchio", "dep:pinocchio-log", "dep:pinocchio-pubkey", @@ -75,12 +73,12 @@ solana-address = { version = "2.0", features = ["bytemuck", "decode", "syscalls" # manually resolves the conflict with a pinned version of serde serde = { version = "1.0.228", features = ["derive"] } -sha2 = { version = "0.10", optional = true } -x25519-dalek = { version = "2", features = ["static_secrets"], optional = true } -curve25519-dalek = { version = "4", optional = true } solana-sdk = { version = ">=1.16", optional = true } rand = { version = "=0.8.5", features = ["small_rng"], optional = true } +[target.'cfg(not(target_os = "solana"))'.dependencies] +libsodium-rs = { version = "0.2.0", optional = true } + [dev-dependencies] assertables = "9.8.2" magicblock-delegation-program = { path = ".", features = ["unit_test_config"] } diff --git a/src/encryption/mod.rs b/src/encryption/mod.rs index 4d7a3ad1..9a65118c 100644 --- a/src/encryption/mod.rs +++ b/src/encryption/mod.rs @@ -1,229 +1,190 @@ -#[cfg(feature = "sdk")] -use curve25519_dalek::{ - edwards::CompressedEdwardsY, montgomery::MontgomeryPoint, -}; -use serde::{Deserialize, Serialize}; -use solana_program::hash::hashv; - -#[cfg(feature = "sdk")] -use sha2::{Digest, Sha512}; -#[cfg(feature = "sdk")] -use x25519_dalek::{PublicKey as X25519Public, StaticSecret as X25519Secret}; +#[cfg(all(feature = "sdk", not(target_os = "solana")))] +use libsodium_rs::{crypto_box, crypto_sign, ensure_init}; pub const KEY_LEN: usize = 32; #[derive(Debug, thiserror::Error)] pub enum EncryptionError { + #[error("libsodium init failed")] + SodiumInitFailed, + #[error("encryption is not available on this target")] + UnsupportedTarget, #[error("invalid ed25519 public key for x25519 conversion")] InvalidEd25519PublicKey, + #[error("invalid ed25519 secret key for x25519 conversion")] + InvalidEd25519SecretKey, + #[error("invalid x25519 public key")] + InvalidX25519PublicKey, + #[error("invalid x25519 secret key")] + InvalidX25519SecretKey, + #[error("failed to encrypt payload")] + SealFailed, + #[error("failed to decrypt payload")] + OpenFailed, } -#[derive(Debug, Serialize, Deserialize)] -pub struct EncryptedPayloadV1 { - /// Ephemeral X25519 public key created by the sender for this payload. - pub ephemeral_pubkey: [u8; KEY_LEN], - - /// Encrypted compact-action bytes. - pub ciphertext: Vec, +#[cfg(all(feature = "sdk", not(target_os = "solana")))] +fn init_sodium() -> Result<(), EncryptionError> { + ensure_init().map_err(|_| EncryptionError::SodiumInitFailed)?; + Ok(()) } /// Convert an Ed25519 public key into an X25519 public key. -#[cfg(feature = "sdk")] +#[cfg(all(feature = "sdk", not(target_os = "solana")))] pub fn ed25519_pubkey_to_x25519( ed25519_pubkey: &[u8; KEY_LEN], -) -> Option<[u8; KEY_LEN]> { - let edwards = CompressedEdwardsY(*ed25519_pubkey).decompress()?; - let montgomery: MontgomeryPoint = edwards.to_montgomery(); - Some(montgomery.to_bytes()) +) -> Result<[u8; KEY_LEN], EncryptionError> { + init_sodium()?; + let ed_pk = crypto_sign::PublicKey::from_bytes(ed25519_pubkey) + .map_err(|_| EncryptionError::InvalidEd25519PublicKey)?; + let x_pk = crypto_sign::ed25519_pk_to_curve25519(&ed_pk) + .map_err(|_| EncryptionError::InvalidEd25519PublicKey)?; + let mut out = [0u8; KEY_LEN]; + out.copy_from_slice(&x_pk); + Ok(out) } -/// Convert an Ed25519 secret key seed into an X25519 secret key. -/// -/// This follows the libsodium-style conversion: -/// SHA-512(seed) then clamp the first 32 bytes. -#[cfg(feature = "sdk")] -pub fn ed25519_secret_to_x25519( - ed25519_secret_seed: &[u8; KEY_LEN], -) -> [u8; KEY_LEN] { - let mut h = Sha512::new(); - h.update(ed25519_secret_seed); - let digest = h.finalize(); +#[cfg(all(feature = "sdk", target_os = "solana"))] +pub fn ed25519_pubkey_to_x25519( + _ed25519_pubkey: &[u8; KEY_LEN], +) -> Result<[u8; KEY_LEN], EncryptionError> { + Err(EncryptionError::UnsupportedTarget) +} +/// Convert an Ed25519 secret key into an X25519 secret key. +#[cfg(all(feature = "sdk", not(target_os = "solana")))] +pub fn ed25519_secret_to_x25519( + ed25519_secret_key: &[u8], +) -> Result<[u8; KEY_LEN], EncryptionError> { + init_sodium()?; + let ed_sk = crypto_sign::SecretKey::from_bytes(ed25519_secret_key) + .map_err(|_| EncryptionError::InvalidEd25519SecretKey)?; + let x_sk = crypto_sign::ed25519_sk_to_curve25519(&ed_sk) + .map_err(|_| EncryptionError::InvalidEd25519SecretKey)?; let mut out = [0u8; KEY_LEN]; - out.copy_from_slice(&digest[..KEY_LEN]); - out[0] &= 248; - out[31] &= 127; - out[31] |= 64; - out + out.copy_from_slice(&x_sk); + Ok(out) +} + +#[cfg(all(feature = "sdk", target_os = "solana"))] +pub fn ed25519_secret_to_x25519( + _ed25519_secret_key: &[u8], +) -> Result<[u8; KEY_LEN], EncryptionError> { + Err(EncryptionError::UnsupportedTarget) } /// Convenience helper for SDK usage: derive X25519 secret key bytes from a Solana Keypair. -#[cfg(feature = "sdk")] +#[cfg(all(feature = "sdk", not(target_os = "solana")))] pub fn keypair_to_x25519_secret( keypair: &solana_sdk::signature::Keypair, -) -> [u8; KEY_LEN] { +) -> Result<[u8; KEY_LEN], EncryptionError> { let keypair_bytes = keypair.to_bytes(); - let mut seed = [0u8; KEY_LEN]; - seed.copy_from_slice(&keypair_bytes[..KEY_LEN]); - ed25519_secret_to_x25519(&seed) + ed25519_secret_to_x25519(&keypair_bytes) } -/// High-level API: encrypt for validator using a random ephemeral secret from OS RNG. -#[cfg(feature = "sdk")] -pub fn encrypt( - plaintext: &[u8], - recipient_x25519_pubkey: &[u8; KEY_LEN], -) -> Vec { - use rand::rngs::OsRng; - let ephemeral_secret = X25519Secret::random_from_rng(OsRng).to_bytes(); - encrypt_with_ephemeral( - plaintext, - recipient_x25519_pubkey, - &ephemeral_secret, - ) +#[cfg(all(feature = "sdk", target_os = "solana"))] +pub fn keypair_to_x25519_secret( + _keypair: &solana_sdk::signature::Keypair, +) -> Result<[u8; KEY_LEN], EncryptionError> { + Err(EncryptionError::UnsupportedTarget) } -/// High-level API: same as above, but starts from recipient Ed25519 pubkey. -#[cfg(feature = "sdk")] +/// High-level API: encrypt for validator using sealed boxes. +#[cfg(all(feature = "sdk", not(target_os = "solana")))] pub fn encrypt_ed25519_recipient( plaintext: &[u8], recipient_ed25519_pubkey: &[u8; KEY_LEN], ) -> Result, EncryptionError> { - let recipient_x25519_pubkey = - ed25519_pubkey_to_x25519(recipient_ed25519_pubkey) - .ok_or(EncryptionError::InvalidEd25519PublicKey)?; - Ok(encrypt(plaintext, &recipient_x25519_pubkey)) + init_sodium()?; + let ed_pk = crypto_sign::PublicKey::from_bytes(recipient_ed25519_pubkey) + .map_err(|_| EncryptionError::InvalidEd25519PublicKey)?; + let x_pk = crypto_sign::ed25519_pk_to_curve25519(&ed_pk) + .map_err(|_| EncryptionError::InvalidEd25519PublicKey)?; + let x_pk = crypto_box::PublicKey::from_bytes_exact(x_pk); + crypto_box::seal_box(plaintext, &x_pk) + .map_err(|_| EncryptionError::SealFailed) } #[cfg(not(feature = "sdk"))] pub fn encrypt_ed25519_recipient( - plaintext: &[u8], - recipient_ed25519_pubkey: &[u8; KEY_LEN], + _plaintext: &[u8], + _recipient_ed25519_pubkey: &[u8; KEY_LEN], ) -> Result, EncryptionError> { panic!("encrypt_ed25519_recipient requires `sdk` feature"); } -/// Encrypt any plaintext bytes for a recipient X25519 public key. -/// -/// The caller supplies an ephemeral secret key (random per message). -/// This function stores the ephemeral public key in the output payload. -/// -/// Example: -/// `let encrypted = encrypt_with_ephemeral(b"hello", &recipient_pubkey, &ephemeral_secret);` -#[cfg(feature = "sdk")] -pub fn encrypt_with_ephemeral( - plaintext: &[u8], - recipient_x25519_pubkey: &[u8; KEY_LEN], - ephemeral_x25519_secret: &[u8; KEY_LEN], -) -> Vec { - let sender_secret = X25519Secret::from(*ephemeral_x25519_secret); - let sender_public = X25519Public::from(&sender_secret); - let recipient_public = X25519Public::from(*recipient_x25519_pubkey); - let shared = sender_secret.diffie_hellman(&recipient_public).to_bytes(); - - let mut ciphertext = plaintext.to_vec(); - xor_with_stream(&mut ciphertext, &shared); - - bincode::serialize(&EncryptedPayloadV1 { - ephemeral_pubkey: sender_public.to_bytes(), - ciphertext, - }) - .expect("encrypted payload serialization should not fail") +#[cfg(all(feature = "sdk", target_os = "solana"))] +pub fn encrypt_ed25519_recipient( + _plaintext: &[u8], + _recipient_ed25519_pubkey: &[u8; KEY_LEN], +) -> Result, EncryptionError> { + Err(EncryptionError::UnsupportedTarget) } -/// Decrypt serialized encrypted payload bytes back to plaintext bytes. -/// -/// Example: -/// `let plaintext = decrypt(&encrypted, &recipient_x25519_secret)?;` -#[cfg(feature = "sdk")] +/// Decrypt sealed box bytes back to plaintext bytes. +#[cfg(all(feature = "sdk", not(target_os = "solana")))] pub fn decrypt( encrypted_payload: &[u8], + recipient_x25519_pubkey: &[u8; KEY_LEN], recipient_x25519_secret: &[u8; KEY_LEN], -) -> Result, bincode::Error> { - let EncryptedPayloadV1 { - ephemeral_pubkey, - mut ciphertext, - } = bincode::deserialize(encrypted_payload)?; - - let recipient_secret = X25519Secret::from(*recipient_x25519_secret); - let sender_public = X25519Public::from(ephemeral_pubkey); - let shared = recipient_secret.diffie_hellman(&sender_public).to_bytes(); - - xor_with_stream(&mut ciphertext, &shared); - Ok(ciphertext) +) -> Result, EncryptionError> { + init_sodium()?; + let pk = crypto_box::PublicKey::from_bytes_exact(*recipient_x25519_pubkey); + let sk = crypto_box::SecretKey::from_bytes_exact(*recipient_x25519_secret); + crypto_box::open_sealed_box(encrypted_payload, &pk, &sk) + .map_err(|_| EncryptionError::OpenFailed) } -fn xor_with_stream(data: &mut [u8], shared_secret: &[u8; KEY_LEN]) { - let mut counter: u64 = 0; - let mut offset = 0usize; - - while offset < data.len() { - let block = hashv(&[shared_secret.as_slice(), &counter.to_le_bytes()]) - .to_bytes(); - - for &k in block.iter() { - if offset >= data.len() { - break; - } - data[offset] ^= k; - offset += 1; - } - - counter = counter.wrapping_add(1); - } +#[cfg(all(feature = "sdk", target_os = "solana"))] +pub fn decrypt( + _encrypted_payload: &[u8], + _recipient_x25519_pubkey: &[u8; KEY_LEN], + _recipient_x25519_secret: &[u8; KEY_LEN], +) -> Result, EncryptionError> { + Err(EncryptionError::UnsupportedTarget) } #[cfg(test)] +#[cfg(all(feature = "sdk", not(target_os = "solana")))] mod tests { - use x25519_dalek::{ - PublicKey as X25519Public, StaticSecret as X25519Secret, - }; + use solana_sdk::signer::Signer; use super::*; - #[test] - fn test_ed25519_secret_to_x25519_shape() { - let seed = [7u8; KEY_LEN]; - let secret = ed25519_secret_to_x25519(&seed); - assert_eq!(secret.len(), KEY_LEN); - assert_eq!(secret[0] & 0b0000_0111, 0); - assert_eq!(secret[31] & 0b1000_0000, 0); - assert_eq!(secret[31] & 0b0100_0000, 0b0100_0000); - } - #[test] fn test_encrypt_decrypt_roundtrip() { - let validator_secret = X25519Secret::from([11u8; KEY_LEN]); - let validator_public = X25519Public::from(&validator_secret).to_bytes(); - let validator_secret = validator_secret.to_bytes(); - let ephemeral_secret = [22u8; KEY_LEN]; + let validator = solana_sdk::signature::Keypair::new(); + let validator_x25519_secret = + keypair_to_x25519_secret(&validator).unwrap(); + let validator_x25519_pubkey = ed25519_pubkey_to_x25519( + validator.pubkey().as_array(), + ) + .unwrap(); let plaintext = b"hello compact actions"; - let encrypted = encrypt_with_ephemeral( - plaintext, - &validator_public, - &ephemeral_secret, - ); - let decrypted = decrypt(&encrypted, &validator_secret).unwrap(); + let encrypted = + encrypt_ed25519_recipient(plaintext, validator.pubkey().as_array()) + .unwrap(); + let decrypted = decrypt( + &encrypted, + &validator_x25519_pubkey, + &validator_x25519_secret, + ) + .unwrap(); assert_eq!(decrypted, plaintext); } #[test] fn test_random_ephemeral_changes_ciphertext() { - let validator_secret = X25519Secret::from([44u8; KEY_LEN]); - let validator_public = X25519Public::from(&validator_secret).to_bytes(); + let validator = solana_sdk::signature::Keypair::new(); let plaintext = b"same bytes"; - let c1 = encrypt_with_ephemeral( - plaintext, - &validator_public, - &[1u8; KEY_LEN], - ); - let c2 = encrypt_with_ephemeral( - plaintext, - &validator_public, - &[2u8; KEY_LEN], - ); + let c1 = + encrypt_ed25519_recipient(plaintext, validator.pubkey().as_array()) + .unwrap(); + let c2 = + encrypt_ed25519_recipient(plaintext, validator.pubkey().as_array()) + .unwrap(); assert_ne!(c1, c2); } } diff --git a/src/instruction_builder/delegate_with_actions.rs b/src/instruction_builder/delegate_with_actions.rs index 9260195f..9cb45cc7 100644 --- a/src/instruction_builder/delegate_with_actions.rs +++ b/src/instruction_builder/delegate_with_actions.rs @@ -29,95 +29,6 @@ pub trait Encryptable: Sized { fn with_encryption(self, encrypt: bool) -> Self::Output; } -pub struct PostDelegationInstruction { - pub program_id: EncryptablePubkey, - pub accounts: Vec, - pub data: EncryptableIxData, -} - -#[derive(Clone, Debug)] -pub struct EncryptableIxData { - pub data: Vec, - - /// [0, encrypt_offset) is cleartext and [encrypt_offset, len) is encrypted - pub encrypt_begin_offset: usize, -} - -impl EncryptableIxData { - fn encrypt(self, validator: &Option) -> MaybeEncryptedIxData { - if self.encrypt_begin_offset >= self.data.len() { - MaybeEncryptedIxData { - prefix: self.data, - suffix: EncryptedBuffer::default(), - } - } else { - let validator = validator.expect(""); - MaybeEncryptedIxData { - prefix: self.data[0..self.encrypt_begin_offset].into(), - // TODO (snawaz): finish it - suffix: { - EncryptedBuffer::new( - crate::encryption::encrypt_ed25519_recipient( - &self.data[self.encrypt_begin_offset..], - validator.as_array(), - ) - .expect(""), - ) - }, - } - } - } -} - -#[derive(Clone, Debug)] -pub struct EncryptablePubkey { - pub pubkey: Pubkey, - pub is_encryptable: bool, -} - -impl Encryptable for Pubkey { - type Output = EncryptablePubkey; - fn with_encryption(self, encrypt: bool) -> Self::Output { - EncryptablePubkey { - pubkey: self, - is_encryptable: encrypt, - } - } -} - -#[derive(Clone, Debug)] -pub struct EncryptableAccountMeta { - pub account_meta: AccountMeta, - pub is_encryptable: bool, -} - -impl EncryptableAccountMeta { - fn encrypt(self, validator: &Option) -> MaybeEncryptedPubkey { - if self.is_encryptable { - let validator = validator.expect(""); - MaybeEncryptedPubkey::Encrypted(EncryptedBuffer::new( - crate::encryption::encrypt_ed25519_recipient( - self.account_meta.pubkey.as_array(), - validator.as_array(), - ) - .expect(""), - )) - } else { - MaybeEncryptedPubkey::ClearText(self.account_meta.pubkey) - } - } -} - -impl Encryptable for AccountMeta { - type Output = EncryptableAccountMeta; - fn with_encryption(self, encrypt: bool) -> Self::Output { - EncryptableAccountMeta { - account_meta: self, - is_encryptable: encrypt, - } - } -} - /// See [crate::processor::process_delegate_with_actions] for docs. pub fn delegate_with_actions( payer: Pubkey, @@ -127,7 +38,7 @@ pub fn delegate_with_actions( actions: Vec, ) -> Instruction { let (actions, signers) = - compact_post_delegation_actions(actions, delegate.validator); + create_post_delegation_actions(actions, delegate.validator); Instruction { program_id: crate::id(), @@ -172,7 +83,7 @@ pub fn delegate_with_actions( } } -fn compact_post_delegation_actions( +fn create_post_delegation_actions( instructions: Vec, validator: Option, ) -> (PostDelegationActions, Vec) { @@ -300,11 +211,102 @@ fn compact_post_delegation_actions( ) } +pub struct PostDelegationInstruction { + pub program_id: EncryptablePubkey, + pub accounts: Vec, + pub data: EncryptableIxData, +} + +#[derive(Clone, Debug)] +pub struct EncryptableIxData { + pub data: Vec, + + /// [0, encrypt_offset) is cleartext and [encrypt_offset, len) is encrypted + pub encrypt_begin_offset: usize, +} + +impl EncryptableIxData { + fn encrypt(self, validator: &Option) -> MaybeEncryptedIxData { + if self.encrypt_begin_offset >= self.data.len() { + MaybeEncryptedIxData { + prefix: self.data, + suffix: EncryptedBuffer::default(), + } + } else { + let validator = validator.expect(""); + MaybeEncryptedIxData { + prefix: self.data[0..self.encrypt_begin_offset].into(), + // TODO (snawaz): finish it + suffix: { + EncryptedBuffer::new( + crate::encryption::encrypt_ed25519_recipient( + &self.data[self.encrypt_begin_offset..], + validator.as_array(), + ) + .expect(""), + ) + }, + } + } + } +} + +#[derive(Clone, Debug)] +pub struct EncryptablePubkey { + pub pubkey: Pubkey, + pub is_encryptable: bool, +} + +impl Encryptable for Pubkey { + type Output = EncryptablePubkey; + fn with_encryption(self, encrypt: bool) -> Self::Output { + EncryptablePubkey { + pubkey: self, + is_encryptable: encrypt, + } + } +} + +#[derive(Clone, Debug)] +pub struct EncryptableAccountMeta { + pub account_meta: AccountMeta, + pub is_encryptable: bool, +} + +impl EncryptableAccountMeta { + fn encrypt(self, validator: &Option) -> MaybeEncryptedPubkey { + if self.is_encryptable { + let validator = validator.expect(""); + MaybeEncryptedPubkey::Encrypted(EncryptedBuffer::new( + crate::encryption::encrypt_ed25519_recipient( + self.account_meta.pubkey.as_array(), + validator.as_array(), + ) + .expect(""), + )) + } else { + MaybeEncryptedPubkey::ClearText(self.account_meta.pubkey) + } + } +} + +impl Encryptable for AccountMeta { + type Output = EncryptableAccountMeta; + fn with_encryption(self, encrypt: bool) -> Self::Output { + EncryptableAccountMeta { + account_meta: self, + is_encryptable: encrypt, + } + } +} + #[cfg(test)] mod tests { use super::*; + use solana_sdk::{signature::Keypair, signer::Signer}; #[test] + #[cfg(feature = "sdk")] fn test_compact_post_delegation_actions() { let a = Pubkey::new_from_array([1; 32]); // 0: signer let b = Pubkey::new_from_array([2; 32]); // 1: non-signer @@ -327,9 +329,10 @@ mod tests { }, }]; - let (actions, _meta_signers) = compact_post_delegation_actions( + let validator = Keypair::new(); + let (actions, _meta_signers) = create_post_delegation_actions( instructions, - Some(Pubkey::new_unique()), + Some(validator.pubkey()), ); // indices: a, c, e, d, b diff --git a/tests/test_call_handler.rs b/tests/test_call_handler.rs index 2f4cfce5..77ad4de7 100644 --- a/tests/test_call_handler.rs +++ b/tests/test_call_handler.rs @@ -15,7 +15,7 @@ use solana_program::{ hash::Hash, instruction::AccountMeta, native_token::LAMPORTS_PER_SOL, rent::Rent, system_program, }; -use solana_program_test::{read_file, BanksClient, ProgramTest}; +use solana_program_test::{processor, read_file, BanksClient, ProgramTest}; use solana_sdk::{ account::Account, pubkey::Pubkey, @@ -253,7 +253,11 @@ async fn setup_ephemeral_balance( } async fn setup_program_test_env() -> (BanksClient, Keypair, Keypair, Hash) { - let mut program_test = ProgramTest::new("dlp", dlp::ID, None); + let mut program_test = ProgramTest::new( + "dlp", + dlp::ID, + processor!(dlp::slow_process_instruction), + ); program_test.prefer_bpf(true); let payer = Keypair::new(); diff --git a/tests/test_delegate_with_actions.rs b/tests/test_delegate_with_actions.rs index 5cf2049b..e98f4099 100644 --- a/tests/test_delegate_with_actions.rs +++ b/tests/test_delegate_with_actions.rs @@ -3,7 +3,7 @@ use dlp::{ compact, instruction_builder::{ delegate_with_actions, Encryptable, EncryptableIxData, - EncryptablePubkey, PostDelegationInstruction, + PostDelegationInstruction, }, }; use solana_program::{instruction::AccountMeta, pubkey::Pubkey}; @@ -12,12 +12,12 @@ use solana_sdk::signer::Signer; #[test] fn test_compact_account_meta_bit_packing() { let packed = compact::AccountMeta::new_readonly(42, true); - assert_eq!(packed.index(), 42); + assert_eq!(packed.key(), 42); assert!(packed.is_signer()); assert!(!packed.is_writable()); let packed = compact::AccountMeta::new(63, false); - assert_eq!(packed.index(), 63); + assert_eq!(packed.key(), 63); assert!(!packed.is_signer()); assert!(packed.is_writable()); @@ -38,7 +38,7 @@ fn test_delegate_with_actions_bincode_roundtrip_compact_payload() { ], data: EncryptableIxData { data: vec![1, 2, 3], - encrypt_offset: 3, + encrypt_begin_offset: 3, }, }, PostDelegationInstruction { @@ -46,7 +46,7 @@ fn test_delegate_with_actions_bincode_roundtrip_compact_payload() { accounts: vec![AccountMeta::new_readonly(signer, true).cleartext()], data: EncryptableIxData { data: vec![9, 9], - encrypt_offset: 2, + encrypt_begin_offset: 2, }, }, ]; @@ -92,7 +92,7 @@ fn test_delegate_with_actions_builder_adds_compact_signers_to_remaining_accounts ], data: EncryptableIxData { data: vec![7, 7], - encrypt_offset: 2, + encrypt_begin_offset: 2, }, }, PostDelegationInstruction { @@ -103,7 +103,7 @@ fn test_delegate_with_actions_builder_adds_compact_signers_to_remaining_accounts ], data: EncryptableIxData { data: vec![8, 8], - encrypt_offset: 2, + encrypt_begin_offset: 2, }, }, ]; @@ -125,13 +125,18 @@ fn test_delegate_with_actions_builder_adds_compact_signers_to_remaining_accounts } #[test] -//#[cfg(feature = "sdk")] +#[cfg(feature = "sdk")] fn test_delegate_with_actions_builder_private_sets_encrypted_payload() { use dlp::encryption; use solana_sdk::signature::Keypair; let validator = Keypair::new(); - let validator_secret = encryption::keypair_to_x25519_secret(&validator); + let validator_x25519_secret = + encryption::keypair_to_x25519_secret(&validator).unwrap(); + let validator_x25519_pubkey = encryption::ed25519_pubkey_to_x25519( + validator.pubkey().as_array(), + ) + .unwrap(); let payer = Pubkey::new_unique(); let signer = Pubkey::new_unique(); @@ -140,7 +145,7 @@ fn test_delegate_with_actions_builder_private_sets_encrypted_payload() { accounts: vec![AccountMeta::new_readonly(signer, true).cleartext()], data: EncryptableIxData { data: vec![4, 2], - encrypt_offset: 1, + encrypt_begin_offset: 1, }, }]; @@ -160,8 +165,11 @@ fn test_delegate_with_actions_builder_private_sets_encrypted_payload() { assert_eq!(args.actions.signers.len(), 1); let ix = &args.actions.instructions[0]; assert_eq!(ix.data.prefix, vec![4]); - let decrypted = - encryption::decrypt(ix.data.suffix.as_bytes(), &validator_secret) - .unwrap(); + let decrypted = encryption::decrypt( + ix.data.suffix.as_bytes(), + &validator_x25519_pubkey, + &validator_x25519_secret, + ) + .unwrap(); assert_eq!(decrypted, vec![2]); } From 28e72f144c1a91b0c65f5a04bbb7bbbc79304764 Mon Sep 17 00:00:00 2001 From: Sarfaraz Nawaz Date: Sat, 28 Feb 2026 18:04:25 +0530 Subject: [PATCH 4/4] split program extracting out dlp-sdk --- Cargo.lock | 17 +- Cargo.toml | 7 +- dlp-api/Cargo.toml | 26 +++ dlp-api/src/cpi/delegate_with_actions.rs | 67 ++++++ dlp-api/src/cpi/mod.rs | 3 + {src => dlp-api/src}/encryption/mod.rs | 62 +----- .../src}/instruction_builder/call_handler.rs | 15 +- .../instruction_builder/call_handler_v2.rs | 15 +- .../close_ephemeral_balance.rs | 11 +- .../close_validator_fees_vault.rs | 13 +- .../src}/instruction_builder/commit_diff.rs | 17 +- .../commit_diff_from_buffer.rs | 17 +- .../instruction_builder/commit_finalize.rs | 23 +- .../commit_finalize_from_buffer.rs | 23 +- .../src}/instruction_builder/commit_state.rs | 17 +- .../commit_state_from_buffer.rs | 17 +- .../src}/instruction_builder/delegate.rs | 19 +- .../delegate_ephemeral_balance.rs | 19 +- .../delegate_with_actions.rs | 201 +++++++----------- .../src}/instruction_builder/finalize.rs | 17 +- .../init_protocol_fees_vault.rs | 7 +- .../init_validator_fees_vault.rs | 13 +- .../src}/instruction_builder/mod.rs | 2 + .../protocol_claim_fees.rs | 13 +- .../top_up_ephemeral_balance.rs | 13 +- .../types/encryptable_types.rs | 149 +++++++++++++ dlp-api/src/instruction_builder/types/mod.rs | 19 ++ .../src}/instruction_builder/undelegate.rs | 17 +- .../undelegate_confined_account.rs | 17 +- .../validator_claim_fees.rs | 15 +- .../whitelist_validator_for_program.rs | 13 +- dlp-api/src/lib.rs | 5 + src/lib.rs | 8 +- src/processor/delegate_ephemeral_balance.rs | 48 ++++- tests/test_call_handler.rs | 22 +- tests/test_call_handler_v2.rs | 43 ++-- tests/test_close_validator_fees_vault.rs | 2 +- tests/test_commit_fees_on_undelegation.rs | 2 +- tests/test_commit_finalize.rs | 4 +- tests/test_commit_finalize_from_buffer.rs | 4 +- tests/test_commit_on_curve.rs | 2 +- tests/test_commit_state.rs | 4 +- tests/test_commit_state_from_buffer.rs | 2 +- .../test_commit_state_with_program_config.rs | 2 +- ...t_undelegate_zero_lamports_system_owned.rs | 6 +- tests/test_delegate_on_curve.rs | 2 +- tests/test_delegate_with_actions.rs | 43 ++-- tests/test_delegation_confined_accounts.rs | 4 +- tests/test_finalize.rs | 2 +- tests/test_init_fees_vault.rs | 3 +- tests/test_init_validator_fees_vault.rs | 4 +- tests/test_lamports_settlement.rs | 6 +- tests/test_protocol_claim_fees.rs | 2 +- tests/test_top_up.rs | 18 +- tests/test_undelegate.rs | 4 +- tests/test_undelegate_confined_account.rs | 2 +- tests/test_undelegate_on_curve.rs | 2 +- tests/test_undelegate_without_commit.rs | 2 +- tests/test_validator_claim_fees.rs | 2 +- tests/test_whitelist_validator_for_program.rs | 6 +- 60 files changed, 650 insertions(+), 490 deletions(-) create mode 100644 dlp-api/Cargo.toml create mode 100644 dlp-api/src/cpi/delegate_with_actions.rs create mode 100644 dlp-api/src/cpi/mod.rs rename {src => dlp-api/src}/encryption/mod.rs (68%) rename {src => dlp-api/src}/instruction_builder/call_handler.rs (95%) rename {src => dlp-api/src}/instruction_builder/call_handler_v2.rs (95%) rename {src => dlp-api/src}/instruction_builder/close_ephemeral_balance.rs (86%) rename {src => dlp-api/src}/instruction_builder/close_validator_fees_vault.rs (88%) rename {src => dlp-api/src}/instruction_builder/commit_diff.rs (96%) rename {src => dlp-api/src}/instruction_builder/commit_diff_from_buffer.rs (96%) rename {src => dlp-api/src}/instruction_builder/commit_finalize.rs (94%) rename {src => dlp-api/src}/instruction_builder/commit_finalize_from_buffer.rs (94%) rename {src => dlp-api/src}/instruction_builder/commit_state.rs (96%) rename {src => dlp-api/src}/instruction_builder/commit_state_from_buffer.rs (96%) rename {src => dlp-api/src}/instruction_builder/delegate.rs (94%) rename {src => dlp-api/src}/instruction_builder/delegate_ephemeral_balance.rs (90%) rename {src => dlp-api/src}/instruction_builder/delegate_with_actions.rs (69%) rename {src => dlp-api/src}/instruction_builder/finalize.rs (96%) rename {src => dlp-api/src}/instruction_builder/init_protocol_fees_vault.rs (76%) rename {src => dlp-api/src}/instruction_builder/init_validator_fees_vault.rs (89%) rename {src => dlp-api/src}/instruction_builder/mod.rs (97%) rename {src => dlp-api/src}/instruction_builder/protocol_claim_fees.rs (86%) rename {src => dlp-api/src}/instruction_builder/top_up_ephemeral_balance.rs (90%) create mode 100644 dlp-api/src/instruction_builder/types/encryptable_types.rs create mode 100644 dlp-api/src/instruction_builder/types/mod.rs rename {src => dlp-api/src}/instruction_builder/undelegate.rs (96%) rename {src => dlp-api/src}/instruction_builder/undelegate_confined_account.rs (92%) rename {src => dlp-api/src}/instruction_builder/validator_claim_fees.rs (89%) rename {src => dlp-api/src}/instruction_builder/whitelist_validator_for_program.rs (92%) create mode 100644 dlp-api/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index dabb6586..4b92d1fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1238,6 +1238,21 @@ dependencies = [ "syn 2.0.93", ] +[[package]] +name = "dlp-api" +version = "0.1.0" +dependencies = [ + "bincode", + "borsh 1.5.7", + "libsodium-rs", + "magicblock-delegation-program", + "rand 0.8.5", + "serde", + "solana-program", + "solana-sdk", + "thiserror 1.0.69", +] + [[package]] name = "downcast" version = "0.11.0" @@ -2493,7 +2508,7 @@ dependencies = [ "borsh 1.5.7", "bytemuck", "const-crypto", - "libsodium-rs", + "dlp-api", "magicblock-delegation-program", "num_enum", "pinocchio 0.10.1", diff --git a/Cargo.toml b/Cargo.toml index 6ccaf9b0..2e25f0d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,9 @@ repository = "https://github.com/magicblock-labs/delegation-program" readme = "./README.md" keywords = ["solana", "crypto", "delegation", "ephemeral-rollups", "magicblock"] +[workspace] +members = [".", "dlp-api"] + [lib] crate-type = ["cdylib", "lib"] name = "dlp" @@ -21,7 +24,6 @@ sdk = [ "no-entrypoint", "dep:solana-sdk", "dep:rand", - "dep:libsodium-rs", "dep:pinocchio", "dep:pinocchio-log", "dep:pinocchio-pubkey", @@ -76,12 +78,11 @@ serde = { version = "1.0.228", features = ["derive"] } solana-sdk = { version = ">=1.16", optional = true } rand = { version = "=0.8.5", features = ["small_rng"], optional = true } -[target.'cfg(not(target_os = "solana"))'.dependencies] -libsodium-rs = { version = "0.2.0", optional = true } [dev-dependencies] assertables = "9.8.2" magicblock-delegation-program = { path = ".", features = ["unit_test_config"] } +dlp-api = { path = "dlp-api" } rand = { version = "=0.8.5", features = ["small_rng"] } solana-program-test = ">=1.16" solana-sdk = ">=1.16" diff --git a/dlp-api/Cargo.toml b/dlp-api/Cargo.toml new file mode 100644 index 00000000..a2d6657d --- /dev/null +++ b/dlp-api/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "dlp-api" +version = "0.1.0" +edition = "2021" +license = "MIT" + +[lib] +name = "dlp_api" + +[dependencies] +# core program crate +dlp = { package = "magicblock-delegation-program", path = ".." } + +# sdk deps +solana-program = { version = ">=1.16, <3.0.0" } +solana-sdk = { version = ">=1.16" } +rand = { version = "=0.8.5", features = ["small_rng"] } +libsodium-rs = { version = "0.2.0" } + +borsh = { version = "1.5.3", features = ["derive"] } +bincode = { version = "^1.3" } +serde = { version = "1.0.228", features = ["derive"] } +thiserror = { version = ">=1" } + +[dev-dependencies] +dlp = { package = "magicblock-delegation-program", path = "..", features = ["unit_test_config"] } diff --git a/dlp-api/src/cpi/delegate_with_actions.rs b/dlp-api/src/cpi/delegate_with_actions.rs new file mode 100644 index 00000000..a8e9451a --- /dev/null +++ b/dlp-api/src/cpi/delegate_with_actions.rs @@ -0,0 +1,67 @@ +use dlp::{ + args::{DelegateArgs, DelegateWithActionsArgs, PostDelegationActions}, + discriminator::DlpDiscriminator, + pda::{ + delegate_buffer_pda_from_delegated_account_and_owner_program, + delegation_metadata_pda_from_delegated_account, + delegation_record_pda_from_delegated_account, + }, +}; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, +}; + +// TODO (snawaz): review it as it is not done yet. jus the basic idea is implemented. + +pub fn delegate_with_actions( + payer: Pubkey, + delegated_account: Pubkey, + owner: Option, + delegate: DelegateArgs, + actions: PostDelegationActions, + actions_signers: Vec, +) -> Instruction { + Instruction { + program_id: dlp::id(), + + accounts: { + let owner = owner.unwrap_or(system_program::id()); + let delegate_buffer_pda = + delegate_buffer_pda_from_delegated_account_and_owner_program( + &delegated_account, + &owner, + ); + let delegation_record_pda = + delegation_record_pda_from_delegated_account( + &delegated_account, + ); + let delegation_metadata_pda = + delegation_metadata_pda_from_delegated_account( + &delegated_account, + ); + + [ + vec![ + AccountMeta::new(payer, true), + AccountMeta::new(delegated_account, true), + AccountMeta::new_readonly(owner, false), + AccountMeta::new(delegate_buffer_pda, false), + AccountMeta::new(delegation_record_pda, false), + AccountMeta::new(delegation_metadata_pda, false), + AccountMeta::new_readonly(system_program::id(), false), + ], + actions_signers, + ] + .concat() + }, + + data: { + let args = DelegateWithActionsArgs { delegate, actions }; + let mut data = DlpDiscriminator::DelegateWithActions.to_vec(); + data.extend_from_slice(&bincode::serialize(&args).unwrap()); + data + }, + } +} diff --git a/dlp-api/src/cpi/mod.rs b/dlp-api/src/cpi/mod.rs new file mode 100644 index 00000000..d9267c2b --- /dev/null +++ b/dlp-api/src/cpi/mod.rs @@ -0,0 +1,3 @@ +mod delegate_with_actions; + +pub use delegate_with_actions::*; diff --git a/src/encryption/mod.rs b/dlp-api/src/encryption/mod.rs similarity index 68% rename from src/encryption/mod.rs rename to dlp-api/src/encryption/mod.rs index 9a65118c..d50e5e37 100644 --- a/src/encryption/mod.rs +++ b/dlp-api/src/encryption/mod.rs @@ -1,4 +1,3 @@ -#[cfg(all(feature = "sdk", not(target_os = "solana")))] use libsodium_rs::{crypto_box, crypto_sign, ensure_init}; pub const KEY_LEN: usize = 32; @@ -7,8 +6,6 @@ pub const KEY_LEN: usize = 32; pub enum EncryptionError { #[error("libsodium init failed")] SodiumInitFailed, - #[error("encryption is not available on this target")] - UnsupportedTarget, #[error("invalid ed25519 public key for x25519 conversion")] InvalidEd25519PublicKey, #[error("invalid ed25519 secret key for x25519 conversion")] @@ -23,14 +20,12 @@ pub enum EncryptionError { OpenFailed, } -#[cfg(all(feature = "sdk", not(target_os = "solana")))] fn init_sodium() -> Result<(), EncryptionError> { ensure_init().map_err(|_| EncryptionError::SodiumInitFailed)?; Ok(()) } /// Convert an Ed25519 public key into an X25519 public key. -#[cfg(all(feature = "sdk", not(target_os = "solana")))] pub fn ed25519_pubkey_to_x25519( ed25519_pubkey: &[u8; KEY_LEN], ) -> Result<[u8; KEY_LEN], EncryptionError> { @@ -44,15 +39,7 @@ pub fn ed25519_pubkey_to_x25519( Ok(out) } -#[cfg(all(feature = "sdk", target_os = "solana"))] -pub fn ed25519_pubkey_to_x25519( - _ed25519_pubkey: &[u8; KEY_LEN], -) -> Result<[u8; KEY_LEN], EncryptionError> { - Err(EncryptionError::UnsupportedTarget) -} - /// Convert an Ed25519 secret key into an X25519 secret key. -#[cfg(all(feature = "sdk", not(target_os = "solana")))] pub fn ed25519_secret_to_x25519( ed25519_secret_key: &[u8], ) -> Result<[u8; KEY_LEN], EncryptionError> { @@ -66,15 +53,7 @@ pub fn ed25519_secret_to_x25519( Ok(out) } -#[cfg(all(feature = "sdk", target_os = "solana"))] -pub fn ed25519_secret_to_x25519( - _ed25519_secret_key: &[u8], -) -> Result<[u8; KEY_LEN], EncryptionError> { - Err(EncryptionError::UnsupportedTarget) -} - /// Convenience helper for SDK usage: derive X25519 secret key bytes from a Solana Keypair. -#[cfg(all(feature = "sdk", not(target_os = "solana")))] pub fn keypair_to_x25519_secret( keypair: &solana_sdk::signature::Keypair, ) -> Result<[u8; KEY_LEN], EncryptionError> { @@ -82,15 +61,7 @@ pub fn keypair_to_x25519_secret( ed25519_secret_to_x25519(&keypair_bytes) } -#[cfg(all(feature = "sdk", target_os = "solana"))] -pub fn keypair_to_x25519_secret( - _keypair: &solana_sdk::signature::Keypair, -) -> Result<[u8; KEY_LEN], EncryptionError> { - Err(EncryptionError::UnsupportedTarget) -} - /// High-level API: encrypt for validator using sealed boxes. -#[cfg(all(feature = "sdk", not(target_os = "solana")))] pub fn encrypt_ed25519_recipient( plaintext: &[u8], recipient_ed25519_pubkey: &[u8; KEY_LEN], @@ -105,24 +76,7 @@ pub fn encrypt_ed25519_recipient( .map_err(|_| EncryptionError::SealFailed) } -#[cfg(not(feature = "sdk"))] -pub fn encrypt_ed25519_recipient( - _plaintext: &[u8], - _recipient_ed25519_pubkey: &[u8; KEY_LEN], -) -> Result, EncryptionError> { - panic!("encrypt_ed25519_recipient requires `sdk` feature"); -} - -#[cfg(all(feature = "sdk", target_os = "solana"))] -pub fn encrypt_ed25519_recipient( - _plaintext: &[u8], - _recipient_ed25519_pubkey: &[u8; KEY_LEN], -) -> Result, EncryptionError> { - Err(EncryptionError::UnsupportedTarget) -} - /// Decrypt sealed box bytes back to plaintext bytes. -#[cfg(all(feature = "sdk", not(target_os = "solana")))] pub fn decrypt( encrypted_payload: &[u8], recipient_x25519_pubkey: &[u8; KEY_LEN], @@ -135,17 +89,7 @@ pub fn decrypt( .map_err(|_| EncryptionError::OpenFailed) } -#[cfg(all(feature = "sdk", target_os = "solana"))] -pub fn decrypt( - _encrypted_payload: &[u8], - _recipient_x25519_pubkey: &[u8; KEY_LEN], - _recipient_x25519_secret: &[u8; KEY_LEN], -) -> Result, EncryptionError> { - Err(EncryptionError::UnsupportedTarget) -} - #[cfg(test)] -#[cfg(all(feature = "sdk", not(target_os = "solana")))] mod tests { use solana_sdk::signer::Signer; @@ -156,10 +100,8 @@ mod tests { let validator = solana_sdk::signature::Keypair::new(); let validator_x25519_secret = keypair_to_x25519_secret(&validator).unwrap(); - let validator_x25519_pubkey = ed25519_pubkey_to_x25519( - validator.pubkey().as_array(), - ) - .unwrap(); + let validator_x25519_pubkey = + ed25519_pubkey_to_x25519(validator.pubkey().as_array()).unwrap(); let plaintext = b"hello compact actions"; let encrypted = diff --git a/src/instruction_builder/call_handler.rs b/dlp-api/src/instruction_builder/call_handler.rs similarity index 95% rename from src/instruction_builder/call_handler.rs rename to dlp-api/src/instruction_builder/call_handler.rs index dba4a20a..1d247dcb 100644 --- a/src/instruction_builder/call_handler.rs +++ b/dlp-api/src/instruction_builder/call_handler.rs @@ -1,10 +1,5 @@ use borsh::to_vec; -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, -}; - -use crate::{ +use dlp::{ args::CallHandlerArgs, discriminator::DlpDiscriminator, pda::{ @@ -13,9 +8,13 @@ use crate::{ }, total_size_budget, AccountSizeClass, DLP_PROGRAM_DATA_SIZE_CLASS, }; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, +}; /// Builds a call handler instruction. -/// See [crate::processor::call_handler] for docs. +/// See [dlp::processor::call_handler] for docs. #[deprecated(since = "1.1.4", note = "Use `call_handler_v2` instead")] pub fn call_handler( validator: Pubkey, @@ -41,7 +40,7 @@ pub fn call_handler( accounts.extend(other_accounts); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts, data: [ DlpDiscriminator::CallHandler.to_vec(), diff --git a/src/instruction_builder/call_handler_v2.rs b/dlp-api/src/instruction_builder/call_handler_v2.rs similarity index 95% rename from src/instruction_builder/call_handler_v2.rs rename to dlp-api/src/instruction_builder/call_handler_v2.rs index 591fbcef..283f87b2 100644 --- a/src/instruction_builder/call_handler_v2.rs +++ b/dlp-api/src/instruction_builder/call_handler_v2.rs @@ -1,10 +1,5 @@ use borsh::to_vec; -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, -}; - -use crate::{ +use dlp::{ args::CallHandlerArgs, discriminator::DlpDiscriminator, pda::{ @@ -13,9 +8,13 @@ use crate::{ }, total_size_budget, AccountSizeClass, DLP_PROGRAM_DATA_SIZE_CLASS, }; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, +}; /// Builds a call handler v2 instruction. -/// See [crate::processor::call_handler_v2] for docs. +/// See [dlp::processor::call_handler_v2] for docs. pub fn call_handler_v2( validator: Pubkey, destination_program: Pubkey, @@ -42,7 +41,7 @@ pub fn call_handler_v2( accounts.extend(other_accounts); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts, data: [ DlpDiscriminator::CallHandlerV2.to_vec(), diff --git a/src/instruction_builder/close_ephemeral_balance.rs b/dlp-api/src/instruction_builder/close_ephemeral_balance.rs similarity index 86% rename from src/instruction_builder/close_ephemeral_balance.rs rename to dlp-api/src/instruction_builder/close_ephemeral_balance.rs index a2b3a5bd..e7a6360e 100644 --- a/src/instruction_builder/close_ephemeral_balance.rs +++ b/dlp-api/src/instruction_builder/close_ephemeral_balance.rs @@ -1,19 +1,18 @@ +use dlp::{ + discriminator::DlpDiscriminator, pda::ephemeral_balance_pda_from_payer, +}; use solana_program::{ instruction::{AccountMeta, Instruction}, pubkey::Pubkey, system_program, }; -use crate::{ - discriminator::DlpDiscriminator, pda::ephemeral_balance_pda_from_payer, -}; - /// Creates instruction to close an ephemeral balance account -/// See [crate::processor::process_close_ephemeral_balance] for docs. +/// See [dlp::processor::process_close_ephemeral_balance] for docs. pub fn close_ephemeral_balance(payer: Pubkey, index: u8) -> Instruction { let ephemeral_balance_pda = ephemeral_balance_pda_from_payer(&payer, index); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new(payer, true), AccountMeta::new(ephemeral_balance_pda, false), diff --git a/src/instruction_builder/close_validator_fees_vault.rs b/dlp-api/src/instruction_builder/close_validator_fees_vault.rs similarity index 88% rename from src/instruction_builder/close_validator_fees_vault.rs rename to dlp-api/src/instruction_builder/close_validator_fees_vault.rs index e489830d..626edcd6 100644 --- a/src/instruction_builder/close_validator_fees_vault.rs +++ b/dlp-api/src/instruction_builder/close_validator_fees_vault.rs @@ -1,15 +1,14 @@ +use dlp::{ + consts::DELEGATION_PROGRAM_DATA_ID, discriminator::DlpDiscriminator, + pda::validator_fees_vault_pda_from_validator, +}; use solana_program::{ instruction::{AccountMeta, Instruction}, pubkey::Pubkey, }; -use crate::{ - consts::DELEGATION_PROGRAM_DATA_ID, discriminator::DlpDiscriminator, - pda::validator_fees_vault_pda_from_validator, -}; - /// Close a validator fees vault PDA. -/// See [crate::processor::process_close_validator_fees_vault] for docs. +/// See [dlp::processor::process_close_validator_fees_vault] for docs. pub fn close_validator_fees_vault( payer: Pubkey, admin: Pubkey, @@ -19,7 +18,7 @@ pub fn close_validator_fees_vault( validator_fees_vault_pda_from_validator(&validator_identity); let delegation_program_data = DELEGATION_PROGRAM_DATA_ID.to_bytes().into(); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new(payer, true), AccountMeta::new(admin, true), diff --git a/src/instruction_builder/commit_diff.rs b/dlp-api/src/instruction_builder/commit_diff.rs similarity index 96% rename from src/instruction_builder/commit_diff.rs rename to dlp-api/src/instruction_builder/commit_diff.rs index f0cacc1d..282322ed 100644 --- a/src/instruction_builder/commit_diff.rs +++ b/dlp-api/src/instruction_builder/commit_diff.rs @@ -1,11 +1,5 @@ use borsh::to_vec; -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - system_program, -}; - -use crate::{ +use dlp::{ args::CommitDiffArgs, discriminator::DlpDiscriminator, pda::{ @@ -18,9 +12,14 @@ use crate::{ }, total_size_budget, AccountSizeClass, DLP_PROGRAM_DATA_SIZE_CLASS, }; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, +}; /// Builds a commit state instruction. -/// See [crate::processor::fast::process_commit_diff] for docs. +/// See [dlp::processor::fast::process_commit_diff] for docs. pub fn commit_diff( validator: Pubkey, delegated_account: Pubkey, @@ -41,7 +40,7 @@ pub fn commit_diff( let program_config_pda = program_config_from_program_id(&delegated_account_owner); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new_readonly(validator, true), AccountMeta::new_readonly(delegated_account, false), diff --git a/src/instruction_builder/commit_diff_from_buffer.rs b/dlp-api/src/instruction_builder/commit_diff_from_buffer.rs similarity index 96% rename from src/instruction_builder/commit_diff_from_buffer.rs rename to dlp-api/src/instruction_builder/commit_diff_from_buffer.rs index 85aafb09..2b53398b 100644 --- a/src/instruction_builder/commit_diff_from_buffer.rs +++ b/dlp-api/src/instruction_builder/commit_diff_from_buffer.rs @@ -1,11 +1,5 @@ use borsh::to_vec; -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - system_program, -}; - -use crate::{ +use dlp::{ args::CommitStateFromBufferArgs, discriminator::DlpDiscriminator, pda::{ @@ -18,9 +12,14 @@ use crate::{ }, total_size_budget, AccountSizeClass, DLP_PROGRAM_DATA_SIZE_CLASS, }; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, +}; /// Builds a commit state from buffer instruction. -/// See [crate::processor::process_commit_diff_from_buffer] for docs. +/// See [dlp::processor::process_commit_diff_from_buffer] for docs. pub fn commit_diff_from_buffer( validator: Pubkey, delegated_account: Pubkey, @@ -42,7 +41,7 @@ pub fn commit_diff_from_buffer( let program_config_pda = program_config_from_program_id(&delegated_account_owner); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new_readonly(validator, true), AccountMeta::new_readonly(delegated_account, false), diff --git a/src/instruction_builder/commit_finalize.rs b/dlp-api/src/instruction_builder/commit_finalize.rs similarity index 94% rename from src/instruction_builder/commit_finalize.rs rename to dlp-api/src/instruction_builder/commit_finalize.rs index d1d83fd5..d4cb4434 100644 --- a/src/instruction_builder/commit_finalize.rs +++ b/dlp-api/src/instruction_builder/commit_finalize.rs @@ -1,10 +1,4 @@ -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - system_program, -}; - -use crate::{ +use dlp::{ args::{CommitBumps, CommitFinalizeArgs}, delegation_metadata_seeds_from_delegated_account, delegation_record_seeds_from_delegated_account, @@ -13,6 +7,11 @@ use crate::{ total_size_budget, validator_fees_vault_seeds_from_validator, AccountSizeClass, DLP_PROGRAM_DATA_SIZE_CLASS, }; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, +}; pub struct CommitPDAs { pub delegation_record: Pubkey, @@ -21,7 +20,7 @@ pub struct CommitPDAs { } /// Builds a commit finalize instruction. -/// See [crate::processor::process_commit_finalize] for docs. +/// See [dlp::processor::process_commit_finalize] for docs. pub fn commit_finalize( validator: Pubkey, delegated_account: Pubkey, @@ -30,17 +29,17 @@ pub fn commit_finalize( ) -> (Instruction, CommitPDAs) { let delegation_record = Pubkey::find_program_address( delegation_record_seeds_from_delegated_account!(delegated_account), - &crate::id(), + &dlp::id(), ); let delegation_metadata = Pubkey::find_program_address( delegation_metadata_seeds_from_delegated_account!(delegated_account), - &crate::id(), + &dlp::id(), ); let validator_fees_vault = Pubkey::find_program_address( validator_fees_vault_seeds_from_validator!(validator), - &crate::id(), + &dlp::id(), ); // save the bumps in the args @@ -52,7 +51,7 @@ pub fn commit_finalize( ( Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new_readonly(validator, true), AccountMeta::new(delegated_account, false), diff --git a/src/instruction_builder/commit_finalize_from_buffer.rs b/dlp-api/src/instruction_builder/commit_finalize_from_buffer.rs similarity index 94% rename from src/instruction_builder/commit_finalize_from_buffer.rs rename to dlp-api/src/instruction_builder/commit_finalize_from_buffer.rs index 77439ea5..12864350 100644 --- a/src/instruction_builder/commit_finalize_from_buffer.rs +++ b/dlp-api/src/instruction_builder/commit_finalize_from_buffer.rs @@ -1,10 +1,4 @@ -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - system_program, -}; - -use crate::{ +use dlp::{ args::{CommitBumps, CommitFinalizeArgs}, delegation_metadata_seeds_from_delegated_account, delegation_record_seeds_from_delegated_account, @@ -13,9 +7,14 @@ use crate::{ total_size_budget, validator_fees_vault_seeds_from_validator, AccountSizeClass, DLP_PROGRAM_DATA_SIZE_CLASS, }; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, +}; /// Builds a commit state from buffer instruction. -/// See [crate::processor::process_commit_diff_from_buffer] for docs. +/// See [dlp::processor::process_commit_diff_from_buffer] for docs. pub fn commit_finalize_from_buffer( validator: Pubkey, delegated_account: Pubkey, @@ -24,17 +23,17 @@ pub fn commit_finalize_from_buffer( ) -> (Instruction, super::CommitPDAs) { let delegation_record = Pubkey::find_program_address( delegation_record_seeds_from_delegated_account!(delegated_account), - &crate::id(), + &dlp::id(), ); let validator_fees_vault = Pubkey::find_program_address( validator_fees_vault_seeds_from_validator!(validator), - &crate::id(), + &dlp::id(), ); let delegation_metadata = Pubkey::find_program_address( delegation_metadata_seeds_from_delegated_account!(delegated_account), - &crate::id(), + &dlp::id(), ); // save the bumps in the args @@ -46,7 +45,7 @@ pub fn commit_finalize_from_buffer( ( Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new_readonly(validator, true), AccountMeta::new(delegated_account, false), diff --git a/src/instruction_builder/commit_state.rs b/dlp-api/src/instruction_builder/commit_state.rs similarity index 96% rename from src/instruction_builder/commit_state.rs rename to dlp-api/src/instruction_builder/commit_state.rs index c30eb74d..d70e6c53 100644 --- a/src/instruction_builder/commit_state.rs +++ b/dlp-api/src/instruction_builder/commit_state.rs @@ -1,11 +1,5 @@ use borsh::to_vec; -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - system_program, -}; - -use crate::{ +use dlp::{ args::CommitStateArgs, discriminator::DlpDiscriminator, pda::{ @@ -18,9 +12,14 @@ use crate::{ }, total_size_budget, AccountSizeClass, DLP_PROGRAM_DATA_SIZE_CLASS, }; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, +}; /// Builds a commit state instruction. -/// See [crate::processor::process_commit_state] for docs. +/// See [dlp::processor::process_commit_state] for docs. pub fn commit_state( validator: Pubkey, delegated_account: Pubkey, @@ -41,7 +40,7 @@ pub fn commit_state( let program_config_pda = program_config_from_program_id(&delegated_account_owner); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new_readonly(validator, true), AccountMeta::new_readonly(delegated_account, false), diff --git a/src/instruction_builder/commit_state_from_buffer.rs b/dlp-api/src/instruction_builder/commit_state_from_buffer.rs similarity index 96% rename from src/instruction_builder/commit_state_from_buffer.rs rename to dlp-api/src/instruction_builder/commit_state_from_buffer.rs index b455b14c..2971167e 100644 --- a/src/instruction_builder/commit_state_from_buffer.rs +++ b/dlp-api/src/instruction_builder/commit_state_from_buffer.rs @@ -1,11 +1,5 @@ use borsh::to_vec; -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - system_program, -}; - -use crate::{ +use dlp::{ args::CommitStateFromBufferArgs, discriminator::DlpDiscriminator, pda::{ @@ -18,9 +12,14 @@ use crate::{ }, total_size_budget, AccountSizeClass, DLP_PROGRAM_DATA_SIZE_CLASS, }; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, +}; /// Builds a commit state from buffer instruction. -/// See [crate::processor::process_commit_state_from_buffer] for docs. +/// See [dlp::processor::process_commit_state_from_buffer] for docs. pub fn commit_state_from_buffer( validator: Pubkey, delegated_account: Pubkey, @@ -42,7 +41,7 @@ pub fn commit_state_from_buffer( let program_config_pda = program_config_from_program_id(&delegated_account_owner); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new_readonly(validator, true), AccountMeta::new_readonly(delegated_account, false), diff --git a/src/instruction_builder/delegate.rs b/dlp-api/src/instruction_builder/delegate.rs similarity index 94% rename from src/instruction_builder/delegate.rs rename to dlp-api/src/instruction_builder/delegate.rs index 9fc5f14f..1f03fee7 100644 --- a/src/instruction_builder/delegate.rs +++ b/dlp-api/src/instruction_builder/delegate.rs @@ -1,11 +1,5 @@ use borsh::to_vec; -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - system_program, -}; - -use crate::{ +use dlp::{ args::DelegateArgs, discriminator::DlpDiscriminator, pda::{ @@ -15,9 +9,14 @@ use crate::{ }, total_size_budget, AccountSizeClass, DLP_PROGRAM_DATA_SIZE_CLASS, }; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, +}; /// Builds a delegate instruction -/// See [crate::processor::process_delegate] for docs. +/// See [dlp::processor::process_delegate] for docs. pub fn delegate( payer: Pubkey, delegated_account: Pubkey, @@ -34,7 +33,7 @@ pub fn delegate( } /// Builds a delegate instruction that allows any validator identity. -/// See [crate::processor::process_delegate_with_any_validator] for docs. +/// See [dlp::processor::process_delegate_with_any_validator] for docs. pub fn delegate_with_any_validator( payer: Pubkey, delegated_account: Pubkey, @@ -71,7 +70,7 @@ fn build_delegate_instruction( data.extend_from_slice(&to_vec(&args).unwrap()); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new(payer, true), AccountMeta::new(delegated_account, true), diff --git a/src/instruction_builder/delegate_ephemeral_balance.rs b/dlp-api/src/instruction_builder/delegate_ephemeral_balance.rs similarity index 90% rename from src/instruction_builder/delegate_ephemeral_balance.rs rename to dlp-api/src/instruction_builder/delegate_ephemeral_balance.rs index e1e8f3cb..51ff287d 100644 --- a/src/instruction_builder/delegate_ephemeral_balance.rs +++ b/dlp-api/src/instruction_builder/delegate_ephemeral_balance.rs @@ -1,11 +1,5 @@ use borsh::to_vec; -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - system_program, -}; - -use crate::{ +use dlp::{ args::DelegateEphemeralBalanceArgs, discriminator::DlpDiscriminator, pda::{ @@ -15,9 +9,14 @@ use crate::{ ephemeral_balance_pda_from_payer, }, }; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, +}; /// Delegate ephemeral balance -/// See [crate::processor::process_delegate_ephemeral_balance] for docs. +/// See [dlp::processor::process_delegate_ephemeral_balance] for docs. pub fn delegate_ephemeral_balance( payer: Pubkey, pubkey: Pubkey, @@ -38,7 +37,7 @@ pub fn delegate_ephemeral_balance( data.extend_from_slice(&to_vec(&args).unwrap()); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new(payer, true), AccountMeta::new_readonly(pubkey, true), @@ -47,7 +46,7 @@ pub fn delegate_ephemeral_balance( AccountMeta::new(delegation_record_pda, false), AccountMeta::new(delegation_metadata_pda, false), AccountMeta::new_readonly(system_program::id(), false), - AccountMeta::new_readonly(crate::id(), false), + AccountMeta::new_readonly(dlp::id(), false), ], data, } diff --git a/src/instruction_builder/delegate_with_actions.rs b/dlp-api/src/instruction_builder/delegate_with_actions.rs similarity index 69% rename from src/instruction_builder/delegate_with_actions.rs rename to dlp-api/src/instruction_builder/delegate_with_actions.rs index 9cb45cc7..b96d603b 100644 --- a/src/instruction_builder/delegate_with_actions.rs +++ b/dlp-api/src/instruction_builder/delegate_with_actions.rs @@ -1,15 +1,5 @@ -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - system_program, -}; - -use crate::{ - args::{ - DelegateArgs, DelegateWithActionsArgs, EncryptedBuffer, - MaybeEncryptedIxData, MaybeEncryptedPubkey, PostDelegationActions, - }, - compact, +use dlp::{ + args::{DelegateArgs, DelegateWithActionsArgs}, discriminator::DlpDiscriminator, pda::{ delegate_buffer_pda_from_delegated_account_and_owner_program, @@ -17,19 +7,15 @@ use crate::{ delegation_record_pda_from_delegated_account, }, }; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, +}; -pub trait Encryptable: Sized { - type Output; - fn encrypted(self) -> Self::Output { - self.with_encryption(true) - } - fn cleartext(self) -> Self::Output { - self.with_encryption(false) - } - fn with_encryption(self, encrypt: bool) -> Self::Output; -} +use super::types::{EncryptableAccountMeta, PostDelegationInstruction}; -/// See [crate::processor::process_delegate_with_actions] for docs. +/// See [dlp::processor::process_delegate_with_actions] for docs. pub fn delegate_with_actions( payer: Pubkey, delegated_account: Pubkey, @@ -41,8 +27,7 @@ pub fn delegate_with_actions( create_post_delegation_actions(actions, delegate.validator); Instruction { - program_id: crate::id(), - + program_id: dlp::id(), accounts: { let owner = owner.unwrap_or(system_program::id()); let delegate_buffer_pda = @@ -73,7 +58,6 @@ pub fn delegate_with_actions( ] .concat() }, - data: { let args = DelegateWithActionsArgs { delegate, actions }; let mut data = DlpDiscriminator::DelegateWithActions.to_vec(); @@ -83,11 +67,11 @@ pub fn delegate_with_actions( } } -fn create_post_delegation_actions( +pub fn create_post_delegation_actions( instructions: Vec, validator: Option, -) -> (PostDelegationActions, Vec) { - use crate::args::MaybeEncryptedInstruction; +) -> (dlp::args::PostDelegationActions, Vec) { + use dlp::args::MaybeEncryptedInstruction; let mut signers: Vec = Vec::new(); let mut add_to_signers = |meta: &EncryptableAccountMeta| { @@ -156,10 +140,10 @@ fn create_post_delegation_actions( } } - if signers.len() + non_signers.len() > compact::MAX_PUBKEYS as usize { + if signers.len() + non_signers.len() > dlp::compact::MAX_PUBKEYS as usize { panic!( "delegate_with_actions supports at most {} unique pubkeys", - compact::MAX_PUBKEYS + dlp::compact::MAX_PUBKEYS ); } @@ -183,7 +167,7 @@ fn create_post_delegation_actions( .accounts .into_iter() .map(|meta| { - compact::AccountMeta::try_new( + dlp::compact::AccountMeta::try_new( index_of(&meta.account_meta.pubkey), meta.account_meta.is_signer, meta.account_meta.is_writable, @@ -197,7 +181,7 @@ fn create_post_delegation_actions( .collect(); ( - PostDelegationActions { + dlp::args::PostDelegationActions { signers: signers.iter().map(|s| s.pubkey).collect(), non_signers: non_signers @@ -211,102 +195,15 @@ fn create_post_delegation_actions( ) } -pub struct PostDelegationInstruction { - pub program_id: EncryptablePubkey, - pub accounts: Vec, - pub data: EncryptableIxData, -} - -#[derive(Clone, Debug)] -pub struct EncryptableIxData { - pub data: Vec, - - /// [0, encrypt_offset) is cleartext and [encrypt_offset, len) is encrypted - pub encrypt_begin_offset: usize, -} - -impl EncryptableIxData { - fn encrypt(self, validator: &Option) -> MaybeEncryptedIxData { - if self.encrypt_begin_offset >= self.data.len() { - MaybeEncryptedIxData { - prefix: self.data, - suffix: EncryptedBuffer::default(), - } - } else { - let validator = validator.expect(""); - MaybeEncryptedIxData { - prefix: self.data[0..self.encrypt_begin_offset].into(), - // TODO (snawaz): finish it - suffix: { - EncryptedBuffer::new( - crate::encryption::encrypt_ed25519_recipient( - &self.data[self.encrypt_begin_offset..], - validator.as_array(), - ) - .expect(""), - ) - }, - } - } - } -} - -#[derive(Clone, Debug)] -pub struct EncryptablePubkey { - pub pubkey: Pubkey, - pub is_encryptable: bool, -} - -impl Encryptable for Pubkey { - type Output = EncryptablePubkey; - fn with_encryption(self, encrypt: bool) -> Self::Output { - EncryptablePubkey { - pubkey: self, - is_encryptable: encrypt, - } - } -} - -#[derive(Clone, Debug)] -pub struct EncryptableAccountMeta { - pub account_meta: AccountMeta, - pub is_encryptable: bool, -} - -impl EncryptableAccountMeta { - fn encrypt(self, validator: &Option) -> MaybeEncryptedPubkey { - if self.is_encryptable { - let validator = validator.expect(""); - MaybeEncryptedPubkey::Encrypted(EncryptedBuffer::new( - crate::encryption::encrypt_ed25519_recipient( - self.account_meta.pubkey.as_array(), - validator.as_array(), - ) - .expect(""), - )) - } else { - MaybeEncryptedPubkey::ClearText(self.account_meta.pubkey) - } - } -} - -impl Encryptable for AccountMeta { - type Output = EncryptableAccountMeta; - fn with_encryption(self, encrypt: bool) -> Self::Output { - EncryptableAccountMeta { - account_meta: self, - is_encryptable: encrypt, - } - } -} - #[cfg(test)] mod tests { - use super::*; + use dlp::args::MaybeEncryptedPubkey; use solana_sdk::{signature::Keypair, signer::Signer}; + use super::*; + use crate::instruction_builder::types::{Encryptable, EncryptableFrom}; + #[test] - #[cfg(feature = "sdk")] fn test_compact_post_delegation_actions() { let a = Pubkey::new_from_array([1; 32]); // 0: signer let b = Pubkey::new_from_array([2; 32]); // 1: non-signer @@ -323,10 +220,7 @@ mod tests { AccountMeta::new_readonly(e, true).cleartext(), // e AccountMeta::new(d, false).encrypted(), // d ], - data: EncryptableIxData { - data: vec![9], - encrypt_begin_offset: 1, - }, + data: vec![9].encrypted_from(1), }]; let validator = Keypair::new(); @@ -349,9 +243,8 @@ mod tests { .iter() .map(|key| match key { MaybeEncryptedPubkey::ClearText(pubkey) => *pubkey, - MaybeEncryptedPubkey::Encrypted(buffer) => { + MaybeEncryptedPubkey::Encrypted(_) => { panic!("there must not be any encrypted pubkeys") - // assert!(!buffer.as_bytes().is_empty()) } }) .collect(); @@ -370,4 +263,52 @@ mod tests { assert_eq!(actions.instructions[0].accounts[3].key(), 2); // e assert_eq!(actions.instructions[0].accounts[4].key(), 3); // d } + + #[test] + fn test_instruction_encrypted() { + let signer = Pubkey::new_unique(); + let nonsigner = Pubkey::new_unique(); + let program_id = Pubkey::new_unique(); + + let enc = Instruction { + program_id, + accounts: vec![ + AccountMeta::new_readonly(signer, true), + AccountMeta::new(nonsigner, false), + ], + data: vec![1, 2, 3], + } + .encrypted(); + + assert_eq!(enc.program_id.pubkey, program_id); + assert!(enc.program_id.is_encryptable); + assert!(enc.accounts[0].is_encryptable); + assert!(enc.accounts[1].is_encryptable); + assert_eq!(enc.data.encrypt_begin_offset, 0); + assert_eq!(enc.data.data, vec![1, 2, 3]); + } + + #[test] + fn test_instruction_encrypted_from() { + let signer = Pubkey::new_unique(); + let nonsigner = Pubkey::new_unique(); + let program_id = Pubkey::new_unique(); + + let enc = Instruction { + program_id, + accounts: vec![ + AccountMeta::new_readonly(signer, true), + AccountMeta::new(nonsigner, false), + ], + data: vec![9, 9, 9, 9, 9, 9], + } + .encrypted_from(4); + + assert_eq!(enc.program_id.pubkey, program_id); + assert!(enc.program_id.is_encryptable); + assert!(enc.accounts[0].is_encryptable); + assert!(enc.accounts[1].is_encryptable); + assert_eq!(enc.data.encrypt_begin_offset, 4); + assert_eq!(enc.data.data, vec![9, 9, 9, 9, 9, 9]); + } } diff --git a/src/instruction_builder/finalize.rs b/dlp-api/src/instruction_builder/finalize.rs similarity index 96% rename from src/instruction_builder/finalize.rs rename to dlp-api/src/instruction_builder/finalize.rs index fe5ff0b3..bff9f77c 100644 --- a/src/instruction_builder/finalize.rs +++ b/dlp-api/src/instruction_builder/finalize.rs @@ -1,10 +1,4 @@ -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - system_program, -}; - -use crate::{ +use dlp::{ discriminator::DlpDiscriminator, pda::{ commit_record_pda_from_delegated_account, @@ -15,9 +9,14 @@ use crate::{ }, total_size_budget, AccountSizeClass, DLP_PROGRAM_DATA_SIZE_CLASS, }; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, +}; /// Builds a finalize state instruction. -/// See [crate::processor::process_finalize] for docs. +/// See [dlp::processor::process_finalize] for docs. pub fn finalize(validator: Pubkey, delegated_account: Pubkey) -> Instruction { let commit_state_pda = commit_state_pda_from_delegated_account(&delegated_account); @@ -30,7 +29,7 @@ pub fn finalize(validator: Pubkey, delegated_account: Pubkey) -> Instruction { let validator_fees_vault_pda = validator_fees_vault_pda_from_validator(&validator); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new_readonly(validator, true), AccountMeta::new(delegated_account, false), diff --git a/src/instruction_builder/init_protocol_fees_vault.rs b/dlp-api/src/instruction_builder/init_protocol_fees_vault.rs similarity index 76% rename from src/instruction_builder/init_protocol_fees_vault.rs rename to dlp-api/src/instruction_builder/init_protocol_fees_vault.rs index 74b2d56e..6da69ea7 100644 --- a/src/instruction_builder/init_protocol_fees_vault.rs +++ b/dlp-api/src/instruction_builder/init_protocol_fees_vault.rs @@ -1,17 +1,16 @@ +use dlp::{discriminator::DlpDiscriminator, pda::fees_vault_pda}; use solana_program::{ instruction::{AccountMeta, Instruction}, pubkey::Pubkey, system_program, }; -use crate::{discriminator::DlpDiscriminator, pda::fees_vault_pda}; - /// Initialize the fees vault PDA. -/// See [crate::processor::process_init_protocol_fees_vault] for docs. +/// See [dlp::processor::process_init_protocol_fees_vault] for docs. pub fn init_protocol_fees_vault(payer: Pubkey) -> Instruction { let fees_vault_pda = fees_vault_pda(); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new(payer, true), AccountMeta::new(fees_vault_pda, false), diff --git a/src/instruction_builder/init_validator_fees_vault.rs b/dlp-api/src/instruction_builder/init_validator_fees_vault.rs similarity index 89% rename from src/instruction_builder/init_validator_fees_vault.rs rename to dlp-api/src/instruction_builder/init_validator_fees_vault.rs index c5601892..07e2d5cc 100644 --- a/src/instruction_builder/init_validator_fees_vault.rs +++ b/dlp-api/src/instruction_builder/init_validator_fees_vault.rs @@ -1,16 +1,15 @@ +use dlp::{ + consts::DELEGATION_PROGRAM_DATA_ID, discriminator::DlpDiscriminator, + pda::validator_fees_vault_pda_from_validator, +}; use solana_program::{ instruction::{AccountMeta, Instruction}, pubkey::Pubkey, system_program, }; -use crate::{ - consts::DELEGATION_PROGRAM_DATA_ID, discriminator::DlpDiscriminator, - pda::validator_fees_vault_pda_from_validator, -}; - /// Initialize a validator fees vault PDA. -/// See [crate::processor::process_init_validator_fees_vault] for docs. +/// See [dlp::processor::process_init_validator_fees_vault] for docs. pub fn init_validator_fees_vault( payer: Pubkey, admin: Pubkey, @@ -20,7 +19,7 @@ pub fn init_validator_fees_vault( validator_fees_vault_pda_from_validator(&validator_identity); let delegation_program_data = DELEGATION_PROGRAM_DATA_ID.to_bytes().into(); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new(payer, true), AccountMeta::new(admin, true), diff --git a/src/instruction_builder/mod.rs b/dlp-api/src/instruction_builder/mod.rs similarity index 97% rename from src/instruction_builder/mod.rs rename to dlp-api/src/instruction_builder/mod.rs index 8334b77c..ba81eb84 100644 --- a/src/instruction_builder/mod.rs +++ b/dlp-api/src/instruction_builder/mod.rs @@ -16,6 +16,7 @@ mod init_protocol_fees_vault; mod init_validator_fees_vault; mod protocol_claim_fees; mod top_up_ephemeral_balance; +mod types; mod undelegate; mod undelegate_confined_account; mod validator_claim_fees; @@ -39,6 +40,7 @@ pub use init_protocol_fees_vault::*; pub use init_validator_fees_vault::*; pub use protocol_claim_fees::*; pub use top_up_ephemeral_balance::*; +pub use types::*; pub use undelegate::*; pub use undelegate_confined_account::*; pub use validator_claim_fees::*; diff --git a/src/instruction_builder/protocol_claim_fees.rs b/dlp-api/src/instruction_builder/protocol_claim_fees.rs similarity index 86% rename from src/instruction_builder/protocol_claim_fees.rs rename to dlp-api/src/instruction_builder/protocol_claim_fees.rs index cbd483ff..98232a9e 100644 --- a/src/instruction_builder/protocol_claim_fees.rs +++ b/dlp-api/src/instruction_builder/protocol_claim_fees.rs @@ -1,20 +1,19 @@ +use dlp::{ + consts::DELEGATION_PROGRAM_DATA_ID, discriminator::DlpDiscriminator, + pda::fees_vault_pda, +}; use solana_program::{ instruction::{AccountMeta, Instruction}, pubkey::Pubkey, }; -use crate::{ - consts::DELEGATION_PROGRAM_DATA_ID, discriminator::DlpDiscriminator, - pda::fees_vault_pda, -}; - /// Claim the accrued fees from the protocol fees vault. -/// See [crate::processor::process_protocol_claim_fees] for docs. +/// See [dlp::processor::process_protocol_claim_fees] for docs. pub fn protocol_claim_fees(admin: Pubkey) -> Instruction { let fees_vault_pda = fees_vault_pda(); let delegation_program_data = DELEGATION_PROGRAM_DATA_ID.to_bytes().into(); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new(admin, true), AccountMeta::new(fees_vault_pda, false), diff --git a/src/instruction_builder/top_up_ephemeral_balance.rs b/dlp-api/src/instruction_builder/top_up_ephemeral_balance.rs similarity index 90% rename from src/instruction_builder/top_up_ephemeral_balance.rs rename to dlp-api/src/instruction_builder/top_up_ephemeral_balance.rs index bc44a290..d64fa819 100644 --- a/src/instruction_builder/top_up_ephemeral_balance.rs +++ b/dlp-api/src/instruction_builder/top_up_ephemeral_balance.rs @@ -1,17 +1,16 @@ use borsh::to_vec; +use dlp::{ + args::TopUpEphemeralBalanceArgs, discriminator::DlpDiscriminator, + pda::ephemeral_balance_pda_from_payer, +}; use solana_program::{ instruction::{AccountMeta, Instruction}, pubkey::Pubkey, system_program, }; -use crate::{ - args::TopUpEphemeralBalanceArgs, discriminator::DlpDiscriminator, - pda::ephemeral_balance_pda_from_payer, -}; - /// Builds a top-up ephemeral balance instruction. -/// See [crate::processor::process_top_up_ephemeral_balance] for docs. +/// See [dlp::processor::process_top_up_ephemeral_balance] for docs. pub fn top_up_ephemeral_balance( payer: Pubkey, pubkey: Pubkey, @@ -25,7 +24,7 @@ pub fn top_up_ephemeral_balance( let ephemeral_balance_pda = ephemeral_balance_pda_from_payer(&pubkey, args.index); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new(payer, true), AccountMeta::new_readonly(pubkey, false), diff --git a/dlp-api/src/instruction_builder/types/encryptable_types.rs b/dlp-api/src/instruction_builder/types/encryptable_types.rs new file mode 100644 index 00000000..fc55183d --- /dev/null +++ b/dlp-api/src/instruction_builder/types/encryptable_types.rs @@ -0,0 +1,149 @@ +use dlp::args::{EncryptedBuffer, MaybeEncryptedIxData, MaybeEncryptedPubkey}; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, +}; + +use crate::instruction_builder::{Encryptable, EncryptableFrom}; + +/// PostDelegationInstruction + Encryptable +pub struct PostDelegationInstruction { + pub program_id: EncryptablePubkey, + pub accounts: Vec, + pub data: EncryptableIxData, +} + +impl Encryptable for Instruction { + type Output = PostDelegationInstruction; + fn with_encryption(self, encrypt: bool) -> Self::Output { + if encrypt { + PostDelegationInstruction { + program_id: self.program_id.encrypted(), + accounts: self + .accounts + .into_iter() + .map(|m| m.encrypted()) + .collect(), + data: self.data.encrypted_from(0), + } + } else { + PostDelegationInstruction { + program_id: self.program_id.cleartext(), + accounts: self + .accounts + .into_iter() + .map(|m| m.cleartext()) + .collect(), + data: self.data.encrypted_from(usize::MAX), + } + } + } +} + +impl EncryptableFrom for Instruction { + type Output = PostDelegationInstruction; + fn encrypted_from(self, offset: usize) -> Self::Output { + PostDelegationInstruction { + program_id: self.program_id.encrypted(), + accounts: self + .accounts + .into_iter() + .map(|m| m.encrypted()) + .collect(), + data: self.data.encrypted_from(offset), + } + } +} + +/// EncryptablePubkey + Encryptable +#[derive(Clone, Debug)] +pub struct EncryptablePubkey { + pub pubkey: Pubkey, + pub is_encryptable: bool, +} + +impl Encryptable for Pubkey { + type Output = EncryptablePubkey; + fn with_encryption(self, encrypt: bool) -> Self::Output { + EncryptablePubkey { + pubkey: self, + is_encryptable: encrypt, + } + } +} + +/// EncryptableAccountMeta + Encryptable +#[derive(Clone, Debug)] +pub struct EncryptableAccountMeta { + pub account_meta: AccountMeta, + pub is_encryptable: bool, +} + +impl EncryptableAccountMeta { + pub fn encrypt(self, validator: &Option) -> MaybeEncryptedPubkey { + if self.is_encryptable { + let validator = validator.expect(""); + MaybeEncryptedPubkey::Encrypted(EncryptedBuffer::new( + crate::encryption::encrypt_ed25519_recipient( + self.account_meta.pubkey.as_array(), + validator.as_array(), + ) + .expect(""), + )) + } else { + MaybeEncryptedPubkey::ClearText(self.account_meta.pubkey) + } + } +} + +impl Encryptable for AccountMeta { + type Output = EncryptableAccountMeta; + fn with_encryption(self, encrypt: bool) -> Self::Output { + EncryptableAccountMeta { + account_meta: self, + is_encryptable: encrypt, + } + } +} + +/// EncryptableIxData + EncryptableFrom +#[derive(Clone, Debug)] +pub struct EncryptableIxData { + pub data: Vec, + + /// [0, encrypt_offset) is cleartext and [encrypt_offset, len) is encrypted + pub encrypt_begin_offset: usize, +} + +impl EncryptableIxData { + pub fn encrypt(self, validator: &Option) -> MaybeEncryptedIxData { + if self.encrypt_begin_offset >= self.data.len() { + MaybeEncryptedIxData { + prefix: self.data, + suffix: EncryptedBuffer::default(), + } + } else { + let validator = validator.expect(""); + MaybeEncryptedIxData { + prefix: self.data[0..self.encrypt_begin_offset].into(), + suffix: EncryptedBuffer::new( + crate::encryption::encrypt_ed25519_recipient( + &self.data[self.encrypt_begin_offset..], + validator.as_array(), + ) + .expect(""), + ), + } + } + } +} + +impl EncryptableFrom for Vec { + type Output = EncryptableIxData; + fn encrypted_from(self, offset: usize) -> Self::Output { + EncryptableIxData { + data: self, + encrypt_begin_offset: offset, + } + } +} diff --git a/dlp-api/src/instruction_builder/types/mod.rs b/dlp-api/src/instruction_builder/types/mod.rs new file mode 100644 index 00000000..01f117aa --- /dev/null +++ b/dlp-api/src/instruction_builder/types/mod.rs @@ -0,0 +1,19 @@ +mod encryptable_types; + +pub use encryptable_types::*; + +pub trait Encryptable: Sized { + type Output; + fn encrypted(self) -> Self::Output { + self.with_encryption(true) + } + fn cleartext(self) -> Self::Output { + self.with_encryption(false) + } + fn with_encryption(self, encrypt: bool) -> Self::Output; +} + +pub trait EncryptableFrom: Sized { + type Output; + fn encrypted_from(self, offset: usize) -> Self::Output; +} diff --git a/src/instruction_builder/undelegate.rs b/dlp-api/src/instruction_builder/undelegate.rs similarity index 96% rename from src/instruction_builder/undelegate.rs rename to dlp-api/src/instruction_builder/undelegate.rs index bc0c4692..bea749de 100644 --- a/src/instruction_builder/undelegate.rs +++ b/dlp-api/src/instruction_builder/undelegate.rs @@ -1,10 +1,4 @@ -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - system_program, -}; - -use crate::{ +use dlp::{ discriminator::DlpDiscriminator, pda::{ commit_record_pda_from_delegated_account, @@ -16,9 +10,14 @@ use crate::{ }, total_size_budget, AccountSizeClass, DLP_PROGRAM_DATA_SIZE_CLASS, }; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, +}; /// Builds an undelegate instruction. -/// See [crate::processor::process_undelegate] for docs. +/// See [dlp::processor::process_undelegate] for docs. #[allow(clippy::too_many_arguments)] pub fn undelegate( validator: Pubkey, @@ -40,7 +39,7 @@ pub fn undelegate( let validator_fees_vault_pda = validator_fees_vault_pda_from_validator(&validator); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new(validator, true), AccountMeta::new(delegated_account, false), diff --git a/src/instruction_builder/undelegate_confined_account.rs b/dlp-api/src/instruction_builder/undelegate_confined_account.rs similarity index 92% rename from src/instruction_builder/undelegate_confined_account.rs rename to dlp-api/src/instruction_builder/undelegate_confined_account.rs index 8a1ccd0a..d4a45a33 100644 --- a/src/instruction_builder/undelegate_confined_account.rs +++ b/dlp-api/src/instruction_builder/undelegate_confined_account.rs @@ -1,10 +1,4 @@ -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - system_program, -}; - -use crate::{ +use dlp::{ consts::DELEGATION_PROGRAM_DATA_ID, discriminator::DlpDiscriminator, pda::{ @@ -13,9 +7,14 @@ use crate::{ undelegate_buffer_pda_from_delegated_account, }, }; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, +}; /// Builds an admin-only undelegate instruction for confined accounts. -/// See [crate::processor::process_undelegate_confined_account] for docs. +/// See [dlp::processor::process_undelegate_confined_account] for docs. pub fn undelegate_confined_account( admin: Pubkey, delegated_account: Pubkey, @@ -30,7 +29,7 @@ pub fn undelegate_confined_account( let delegation_program_data = DELEGATION_PROGRAM_DATA_ID.to_bytes().into(); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new(admin, true), AccountMeta::new(delegated_account, false), diff --git a/src/instruction_builder/validator_claim_fees.rs b/dlp-api/src/instruction_builder/validator_claim_fees.rs similarity index 89% rename from src/instruction_builder/validator_claim_fees.rs rename to dlp-api/src/instruction_builder/validator_claim_fees.rs index e49f642e..efb0709d 100644 --- a/src/instruction_builder/validator_claim_fees.rs +++ b/dlp-api/src/instruction_builder/validator_claim_fees.rs @@ -1,17 +1,16 @@ use borsh::to_vec; -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, -}; - -use crate::{ +use dlp::{ args::ValidatorClaimFeesArgs, discriminator::DlpDiscriminator, pda::{fees_vault_pda, validator_fees_vault_pda_from_validator}, }; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, +}; /// Claim the accrued fees from the fees vault. -/// See [crate::processor::process_validator_claim_fees] for docs. +/// See [dlp::processor::process_validator_claim_fees] for docs. pub fn validator_claim_fees( validator: Pubkey, amount: Option, @@ -21,7 +20,7 @@ pub fn validator_claim_fees( let validator_fees_vault_pda = validator_fees_vault_pda_from_validator(&validator); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new(validator, true), AccountMeta::new(fees_vault_pda, false), diff --git a/src/instruction_builder/whitelist_validator_for_program.rs b/dlp-api/src/instruction_builder/whitelist_validator_for_program.rs similarity index 92% rename from src/instruction_builder/whitelist_validator_for_program.rs rename to dlp-api/src/instruction_builder/whitelist_validator_for_program.rs index 204b0a2a..30ff7544 100644 --- a/src/instruction_builder/whitelist_validator_for_program.rs +++ b/dlp-api/src/instruction_builder/whitelist_validator_for_program.rs @@ -1,4 +1,8 @@ use borsh::to_vec; +use dlp::{ + args::WhitelistValidatorForProgramArgs, consts::DELEGATION_PROGRAM_DATA_ID, + discriminator::DlpDiscriminator, pda::program_config_from_program_id, +}; use solana_program::{ bpf_loader_upgradeable, instruction::{AccountMeta, Instruction}, @@ -6,14 +10,9 @@ use solana_program::{ system_program, }; -use crate::{ - args::WhitelistValidatorForProgramArgs, consts::DELEGATION_PROGRAM_DATA_ID, - discriminator::DlpDiscriminator, pda::program_config_from_program_id, -}; - /// Whitelist validator for program /// -/// See [crate::processor::process_whitelist_validator_for_program] for docs. +/// See [dlp::processor::process_whitelist_validator_for_program] for docs. pub fn whitelist_validator_for_program( authority: Pubkey, validator_identity: Pubkey, @@ -29,7 +28,7 @@ pub fn whitelist_validator_for_program( let delegation_program_data = DELEGATION_PROGRAM_DATA_ID.to_bytes().into(); let program_config_pda = program_config_from_program_id(&program); Instruction { - program_id: crate::id(), + program_id: dlp::id(), accounts: vec![ AccountMeta::new(authority, true), AccountMeta::new_readonly(validator_identity, false), diff --git a/dlp-api/src/lib.rs b/dlp-api/src/lib.rs new file mode 100644 index 00000000..5bc79256 --- /dev/null +++ b/dlp-api/src/lib.rs @@ -0,0 +1,5 @@ +pub use dlp; + +pub mod cpi; +pub mod encryption; +pub mod instruction_builder; diff --git a/src/lib.rs b/src/lib.rs index f3905958..877d9de8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,10 +12,8 @@ //); use solana_program::declare_id; - #[cfg(feature = "logging")] use solana_program::msg; - #[cfg(not(feature = "sdk"))] use { crate::discriminator::DlpDiscriminator, @@ -28,17 +26,14 @@ use { pub mod args; pub mod compact; pub mod consts; -mod discriminator; +pub mod discriminator; pub mod error; -pub mod instruction_builder; pub mod pda; pub mod pod_view; pub mod state; mod account_size_class; -pub mod encryption; - pub use account_size_class::*; //#[cfg(not(feature = "sdk"))] @@ -49,7 +44,6 @@ mod processor; //#[cfg(not(feature = "sdk"))] pub use diff::*; - // re-export //#[cfg(not(feature = "sdk"))] pub use rkyv; diff --git a/src/processor/delegate_ephemeral_balance.rs b/src/processor/delegate_ephemeral_balance.rs index c4023918..60993834 100644 --- a/src/processor/delegate_ephemeral_balance.rs +++ b/src/processor/delegate_ephemeral_balance.rs @@ -1,13 +1,23 @@ use borsh::BorshDeserialize; use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, - program::invoke_signed, program_error::ProgramError, pubkey::Pubkey, + account_info::AccountInfo, + entrypoint::ProgramResult, + instruction::{AccountMeta, Instruction}, + program::invoke_signed, + program_error::ProgramError, + pubkey::Pubkey, system_instruction, system_program, }; use crate::{ args::DelegateEphemeralBalanceArgs, + discriminator::DlpDiscriminator, ephemeral_balance_seeds_from_payer, + pda::{ + delegate_buffer_pda_from_delegated_account_and_owner_program, + delegation_metadata_pda_from_delegated_account, + delegation_record_pda_from_delegated_account, + }, processor::utils::loaders::{load_program, load_signer}, }; @@ -78,13 +88,35 @@ pub fn process_delegate_ephemeral_balance( &[&ephemeral_balance_signer_seeds], )?; - // Create the delegation ix - let ix = crate::instruction_builder::delegate( - *payer.key, - *ephemeral_balance_account.key, - Some(system_program::id()), - args.delegate_args, + let delegate_buffer_pda = + delegate_buffer_pda_from_delegated_account_and_owner_program( + ephemeral_balance_account.key, + &system_program::id(), + ); + let delegation_record_pda = delegation_record_pda_from_delegated_account( + ephemeral_balance_account.key, ); + let delegation_metadata_pda = + delegation_metadata_pda_from_delegated_account( + ephemeral_balance_account.key, + ); + let mut data = DlpDiscriminator::Delegate.to_vec(); + data.extend_from_slice(&borsh::to_vec(&args.delegate_args).unwrap()); + + // Create the delegation ix + let ix = Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(*payer.key, true), + AccountMeta::new(*ephemeral_balance_account.key, true), + AccountMeta::new_readonly(system_program::id(), false), + AccountMeta::new(delegate_buffer_pda, false), + AccountMeta::new(delegation_record_pda, false), + AccountMeta::new(delegation_metadata_pda, false), + AccountMeta::new_readonly(system_program::id(), false), + ], + data, + }; // Invoke signed delegation instruction invoke_signed( diff --git a/tests/test_call_handler.rs b/tests/test_call_handler.rs index 77ad4de7..7b58ebaf 100644 --- a/tests/test_call_handler.rs +++ b/tests/test_call_handler.rs @@ -331,11 +331,11 @@ async fn test_finalize_call_handler() { let (banks, payer, validator, blockhash) = setup_program_test_env().await; let transfer_destination = Keypair::new(); - let finalize_ix = dlp::instruction_builder::finalize( + let finalize_ix = dlp_api::instruction_builder::finalize( validator.pubkey(), DELEGATED_PDA_ID, ); - let call_handler_ix = dlp::instruction_builder::call_handler( + let call_handler_ix = dlp_api::instruction_builder::call_handler( validator.pubkey(), DELEGATED_PDA_OWNER_ID, // destination program payer.pubkey(), // escrow authority @@ -379,17 +379,17 @@ async fn test_undelegate_call_handler() { let (banks, payer, validator, blockhash) = setup_program_test_env().await; let transfer_destination = Keypair::new(); - let finalize_ix = dlp::instruction_builder::finalize( + let finalize_ix = dlp_api::instruction_builder::finalize( validator.pubkey(), DELEGATED_PDA_ID, ); - let undelegate_ix = dlp::instruction_builder::undelegate( + let undelegate_ix = dlp_api::instruction_builder::undelegate( validator.pubkey(), DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, validator.pubkey(), ); - let call_handler_ix = dlp::instruction_builder::call_handler( + let call_handler_ix = dlp_api::instruction_builder::call_handler( validator.pubkey(), DELEGATED_PDA_OWNER_ID, // destination program payer.pubkey(), // escrow authority @@ -446,11 +446,11 @@ async fn test_finalize_invalid_escrow_call_handler() { // Submit the finalize with handler tx let transfer_destination = Keypair::new(); - let finalize_ix = dlp::instruction_builder::finalize( + let finalize_ix = dlp_api::instruction_builder::finalize( authority.pubkey(), DELEGATED_PDA_ID, ); - let call_handler_ix = dlp::instruction_builder::call_handler( + let call_handler_ix = dlp_api::instruction_builder::call_handler( authority.pubkey(), DELEGATED_PDA_OWNER_ID, // destination program DELEGATED_PDA_ID, @@ -481,11 +481,11 @@ async fn test_undelegate_invalid_escow_call_handler() { // Submit the finalize with handler tx let destination = Keypair::new(); - let finalize_ix = dlp::instruction_builder::finalize( + let finalize_ix = dlp_api::instruction_builder::finalize( authority.pubkey(), DELEGATED_PDA_ID, ); - let finalize_call_handler_ix = dlp::instruction_builder::call_handler( + let finalize_call_handler_ix = dlp_api::instruction_builder::call_handler( authority.pubkey(), DELEGATED_PDA_OWNER_ID, // handler program DELEGATED_PDA_ID, @@ -496,13 +496,13 @@ async fn test_undelegate_invalid_escow_call_handler() { }, ); - let undelegate_ix = dlp::instruction_builder::undelegate( + let undelegate_ix = dlp_api::instruction_builder::undelegate( authority.pubkey(), DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, authority.pubkey(), ); - let undelegate_call_handler_ix = dlp::instruction_builder::call_handler( + let undelegate_call_handler_ix = dlp_api::instruction_builder::call_handler( authority.pubkey(), DELEGATED_PDA_OWNER_ID, // handler program DELEGATED_PDA_ID, diff --git a/tests/test_call_handler_v2.rs b/tests/test_call_handler_v2.rs index 03b5f691..f8edebf8 100644 --- a/tests/test_call_handler_v2.rs +++ b/tests/test_call_handler_v2.rs @@ -327,11 +327,11 @@ async fn test_finalize_call_handler_v2() { let (banks, payer, validator, blockhash) = setup_program_test_env().await; let transfer_destination = Keypair::new(); - let finalize_ix = dlp::instruction_builder::finalize( + let finalize_ix = dlp_api::instruction_builder::finalize( validator.pubkey(), DELEGATED_PDA_ID, ); - let call_handler_v2_ix = dlp::instruction_builder::call_handler_v2( + let call_handler_v2_ix = dlp_api::instruction_builder::call_handler_v2( validator.pubkey(), DELEGATED_PDA_OWNER_ID, // destination program DELEGATED_PDA_OWNER_ID, // source program @@ -376,17 +376,17 @@ async fn test_undelegate_call_handler_v2() { let (banks, payer, validator, blockhash) = setup_program_test_env().await; let transfer_destination = Keypair::new(); - let finalize_ix = dlp::instruction_builder::finalize( + let finalize_ix = dlp_api::instruction_builder::finalize( validator.pubkey(), DELEGATED_PDA_ID, ); - let undelegate_ix = dlp::instruction_builder::undelegate( + let undelegate_ix = dlp_api::instruction_builder::undelegate( validator.pubkey(), DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, validator.pubkey(), ); - let call_handler_v2_ix = dlp::instruction_builder::call_handler_v2( + let call_handler_v2_ix = dlp_api::instruction_builder::call_handler_v2( validator.pubkey(), DELEGATED_PDA_OWNER_ID, // destination program DELEGATED_PDA_OWNER_ID, // source program @@ -442,11 +442,11 @@ async fn test_finalize_invalid_escrow_call_handler_v2() { // Submit the finalize with handler tx let transfer_destination = Keypair::new(); - let finalize_ix = dlp::instruction_builder::finalize( + let finalize_ix = dlp_api::instruction_builder::finalize( authority.pubkey(), DELEGATED_PDA_ID, ); - let call_handler_v2_ix = dlp::instruction_builder::call_handler_v2( + let call_handler_v2_ix = dlp_api::instruction_builder::call_handler_v2( authority.pubkey(), DELEGATED_PDA_OWNER_ID, // destination program DELEGATED_PDA_OWNER_ID, // source program @@ -478,30 +478,31 @@ async fn test_undelegate_invalid_escrow_call_handler_v2() { // Submit the finalize with handler tx let destination = Keypair::new(); - let finalize_ix = dlp::instruction_builder::finalize( + let finalize_ix = dlp_api::instruction_builder::finalize( authority.pubkey(), DELEGATED_PDA_ID, ); - let finalize_call_handler_v2_ix = dlp::instruction_builder::call_handler_v2( - authority.pubkey(), - DELEGATED_PDA_OWNER_ID, // handler program - DELEGATED_PDA_OWNER_ID, // source program - DELEGATED_PDA_ID, - vec![AccountMeta::new(destination.pubkey(), false)], - CallHandlerArgs { - escrow_index: 0, - data: UNDELEGATE_HANDLER_V2_DISCRIMINATOR.to_vec(), - }, - ); + let finalize_call_handler_v2_ix = + dlp_api::instruction_builder::call_handler_v2( + authority.pubkey(), + DELEGATED_PDA_OWNER_ID, // handler program + DELEGATED_PDA_OWNER_ID, // source program + DELEGATED_PDA_ID, + vec![AccountMeta::new(destination.pubkey(), false)], + CallHandlerArgs { + escrow_index: 0, + data: UNDELEGATE_HANDLER_V2_DISCRIMINATOR.to_vec(), + }, + ); - let undelegate_ix = dlp::instruction_builder::undelegate( + let undelegate_ix = dlp_api::instruction_builder::undelegate( authority.pubkey(), DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, authority.pubkey(), ); let undelegate_call_handler_v2_ix = - dlp::instruction_builder::call_handler_v2( + dlp_api::instruction_builder::call_handler_v2( authority.pubkey(), DELEGATED_PDA_OWNER_ID, // handler program DELEGATED_PDA_OWNER_ID, // source program diff --git a/tests/test_close_validator_fees_vault.rs b/tests/test_close_validator_fees_vault.rs index e0174b9d..6277012c 100644 --- a/tests/test_close_validator_fees_vault.rs +++ b/tests/test_close_validator_fees_vault.rs @@ -22,7 +22,7 @@ async fn test_close_validator_fees_vault() { validator_fees_vault_pda_from_validator(&validator.pubkey()); // Submit the close vault tx - let ix = dlp::instruction_builder::close_validator_fees_vault( + let ix = dlp_api::instruction_builder::close_validator_fees_vault( admin.pubkey(), admin.pubkey(), validator.pubkey(), diff --git a/tests/test_commit_fees_on_undelegation.rs b/tests/test_commit_fees_on_undelegation.rs index 9f6417e1..8283b289 100644 --- a/tests/test_commit_fees_on_undelegation.rs +++ b/tests/test_commit_fees_on_undelegation.rs @@ -53,7 +53,7 @@ async fn test_commit_fees_on_undelegation() { .min(record_rent + metadata_rent); let expected_fees_vault_fee = expected_total_fees / 10; - let ix = dlp::instruction_builder::undelegate( + let ix = dlp_api::instruction_builder::undelegate( validator.pubkey(), DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, diff --git a/tests/test_commit_finalize.rs b/tests/test_commit_finalize.rs index 1ba144be..1cec81a9 100644 --- a/tests/test_commit_finalize.rs +++ b/tests/test_commit_finalize.rs @@ -49,7 +49,7 @@ async fn run_test_commit_finalize( let new_account_balance = 1_000_000; - let (ix, pdas) = dlp::instruction_builder::commit_finalize( + let (ix, pdas) = dlp_api::instruction_builder::commit_finalize( authority.pubkey(), DELEGATED_PDA_ID, &mut CommitFinalizeArgs { @@ -121,7 +121,7 @@ async fn test_commit_finalize_out_of_order() { let new_account_balance = 1_000_000; - let (ix, _pdas) = dlp::instruction_builder::commit_finalize( + let (ix, _pdas) = dlp_api::instruction_builder::commit_finalize( authority.pubkey(), DELEGATED_PDA_ID, &mut CommitFinalizeArgs { diff --git a/tests/test_commit_finalize_from_buffer.rs b/tests/test_commit_finalize_from_buffer.rs index 907b4a09..06d49fad 100644 --- a/tests/test_commit_finalize_from_buffer.rs +++ b/tests/test_commit_finalize_from_buffer.rs @@ -39,7 +39,7 @@ async fn test_commit_finalize_from_buffer_perf() { let state_buffer_pda = Pubkey::find_program_address(&[b"state_buffer"], &authority.pubkey()).0; - let (ix, pdas) = dlp::instruction_builder::commit_finalize_from_buffer( + let (ix, pdas) = dlp_api::instruction_builder::commit_finalize_from_buffer( authority.pubkey(), DELEGATED_PDA_ID, state_buffer_pda, @@ -107,7 +107,7 @@ async fn test_commit_finalize_from_buffer_out_of_order() { Pubkey::find_program_address(&[b"state_buffer"], &authority.pubkey()).0; let new_account_balance = 1_000_000; - let (ix, _pdas) = dlp::instruction_builder::commit_finalize_from_buffer( + let (ix, _pdas) = dlp_api::instruction_builder::commit_finalize_from_buffer( authority.pubkey(), DELEGATED_PDA_ID, state_buffer_pda, diff --git a/tests/test_commit_on_curve.rs b/tests/test_commit_on_curve.rs index 47ac0f93..cf9daf77 100644 --- a/tests/test_commit_on_curve.rs +++ b/tests/test_commit_on_curve.rs @@ -41,7 +41,7 @@ async fn test_commit_on_curve() { }; // Commit the state for the delegated account - let ix = dlp::instruction_builder::commit_state( + let ix = dlp_api::instruction_builder::commit_state( validator.pubkey(), payer_delegated.pubkey(), system_program::ID, diff --git a/tests/test_commit_state.rs b/tests/test_commit_state.rs index e783446c..807f8a20 100644 --- a/tests/test_commit_state.rs +++ b/tests/test_commit_state.rs @@ -41,7 +41,7 @@ async fn test_commit_new_state() { }; // Commit the state for the delegated account - let ix = dlp::instruction_builder::commit_state( + let ix = dlp_api::instruction_builder::commit_state( authority.pubkey(), DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, @@ -118,7 +118,7 @@ async fn test_commit_out_of_order() { }; // Commit the state for the delegated account - let ix = dlp::instruction_builder::commit_state( + let ix = dlp_api::instruction_builder::commit_state( authority.pubkey(), DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, diff --git a/tests/test_commit_state_from_buffer.rs b/tests/test_commit_state_from_buffer.rs index e6901a64..3dfbe22b 100644 --- a/tests/test_commit_state_from_buffer.rs +++ b/tests/test_commit_state_from_buffer.rs @@ -44,7 +44,7 @@ async fn test_commit_new_state_from_buffer() { }; // Commit the state for the delegated account - let ix = dlp::instruction_builder::commit_state_from_buffer( + let ix = dlp_api::instruction_builder::commit_state_from_buffer( authority.pubkey(), DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, diff --git a/tests/test_commit_state_with_program_config.rs b/tests/test_commit_state_with_program_config.rs index eb3d2443..473d4afc 100644 --- a/tests/test_commit_state_with_program_config.rs +++ b/tests/test_commit_state_with_program_config.rs @@ -53,7 +53,7 @@ async fn test_commit_new_state(valid_config: bool) { }; // Commit the state for the delegated account - let ix = dlp::instruction_builder::commit_state( + let ix = dlp_api::instruction_builder::commit_state( authority.pubkey(), DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, diff --git a/tests/test_commit_undelegate_zero_lamports_system_owned.rs b/tests/test_commit_undelegate_zero_lamports_system_owned.rs index 2246742d..242a88ca 100644 --- a/tests/test_commit_undelegate_zero_lamports_system_owned.rs +++ b/tests/test_commit_undelegate_zero_lamports_system_owned.rs @@ -44,7 +44,7 @@ async fn test_commit_and_undelegate_zero_lamports_system_owned_account() { lamports: 0, }; - let ix_commit = dlp::instruction_builder::commit_state( + let ix_commit = dlp_api::instruction_builder::commit_state( validator.pubkey(), DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, @@ -59,11 +59,11 @@ async fn test_commit_and_undelegate_zero_lamports_system_owned_account() { let res_commit = banks.process_transaction(tx_commit).await; assert!(res_commit.is_ok()); - let ix_finalize = dlp::instruction_builder::finalize( + let ix_finalize = dlp_api::instruction_builder::finalize( validator.pubkey(), DELEGATED_PDA_ID, ); - let ix_undelegate = dlp::instruction_builder::undelegate( + let ix_undelegate = dlp_api::instruction_builder::undelegate( validator.pubkey(), DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, diff --git a/tests/test_delegate_on_curve.rs b/tests/test_delegate_on_curve.rs index 93efb27c..af9085a4 100644 --- a/tests/test_delegate_on_curve.rs +++ b/tests/test_delegate_on_curve.rs @@ -55,7 +55,7 @@ async fn test_delegate_on_curve() { assert_eq!(updated_alt_payer_account.owner, dlp::id()); // Submit the delegate tx - let ix = dlp::instruction_builder::delegate( + let ix = dlp_api::instruction_builder::delegate( payer.pubkey(), delegated_account, None, diff --git a/tests/test_delegate_with_actions.rs b/tests/test_delegate_with_actions.rs index e98f4099..a69d0406 100644 --- a/tests/test_delegate_with_actions.rs +++ b/tests/test_delegate_with_actions.rs @@ -1,10 +1,10 @@ use dlp::{ args::{DelegateArgs, DelegateWithActionsArgs}, compact, - instruction_builder::{ - delegate_with_actions, Encryptable, EncryptableIxData, - PostDelegationInstruction, - }, +}; +use dlp_api::instruction_builder::{ + delegate_with_actions, Encryptable, EncryptableFrom, + PostDelegationInstruction, }; use solana_program::{instruction::AccountMeta, pubkey::Pubkey}; use solana_sdk::signer::Signer; @@ -36,18 +36,12 @@ fn test_delegate_with_actions_bincode_roundtrip_compact_payload() { AccountMeta::new_readonly(payer, true).cleartext(), AccountMeta::new(Pubkey::new_unique(), false).cleartext(), ], - data: EncryptableIxData { - data: vec![1, 2, 3], - encrypt_begin_offset: 3, - }, + data: vec![1, 2, 3].encrypted_from(3), }, PostDelegationInstruction { program_id: Pubkey::new_unique().cleartext(), accounts: vec![AccountMeta::new_readonly(signer, true).cleartext()], - data: EncryptableIxData { - data: vec![9, 9], - encrypt_begin_offset: 2, - }, + data: vec![9, 9].encrypted_from(2), }, ]; @@ -90,10 +84,7 @@ fn test_delegate_with_actions_builder_adds_compact_signers_to_remaining_accounts AccountMeta::new_readonly(signer_a, true).cleartext(), AccountMeta::new_readonly(signer_b, true).cleartext(), ], - data: EncryptableIxData { - data: vec![7, 7], - encrypt_begin_offset: 2, - }, + data: vec![7, 7].encrypted_from(2), }, PostDelegationInstruction { program_id: Pubkey::new_unique().cleartext(), @@ -101,10 +92,7 @@ fn test_delegate_with_actions_builder_adds_compact_signers_to_remaining_accounts AccountMeta::new_readonly(signer_a, true).cleartext(), AccountMeta::new(Pubkey::new_unique(), false).cleartext(), ], - data: EncryptableIxData { - data: vec![8, 8], - encrypt_begin_offset: 2, - }, + data: vec![8, 8].encrypted_from(2), }, ]; @@ -125,28 +113,23 @@ fn test_delegate_with_actions_builder_adds_compact_signers_to_remaining_accounts } #[test] -#[cfg(feature = "sdk")] fn test_delegate_with_actions_builder_private_sets_encrypted_payload() { - use dlp::encryption; + use dlp_api::encryption; use solana_sdk::signature::Keypair; let validator = Keypair::new(); let validator_x25519_secret = encryption::keypair_to_x25519_secret(&validator).unwrap(); - let validator_x25519_pubkey = encryption::ed25519_pubkey_to_x25519( - validator.pubkey().as_array(), - ) - .unwrap(); + let validator_x25519_pubkey = + encryption::ed25519_pubkey_to_x25519(validator.pubkey().as_array()) + .unwrap(); let payer = Pubkey::new_unique(); let signer = Pubkey::new_unique(); let instructions = vec![PostDelegationInstruction { program_id: Pubkey::new_unique().cleartext(), accounts: vec![AccountMeta::new_readonly(signer, true).cleartext()], - data: EncryptableIxData { - data: vec![4, 2], - encrypt_begin_offset: 1, - }, + data: vec![4, 2].encrypted_from(1), }]; let ix = delegate_with_actions( diff --git a/tests/test_delegation_confined_accounts.rs b/tests/test_delegation_confined_accounts.rs index eac2f9b5..4ddcc871 100644 --- a/tests/test_delegation_confined_accounts.rs +++ b/tests/test_delegation_confined_accounts.rs @@ -34,7 +34,7 @@ async fn test_delegation_confined_accounts_rejects_system_validator() { let assign_res = banks.process_transaction(assign_tx).await; assert!(assign_res.is_ok()); - let ix = dlp::instruction_builder::delegate( + let ix = dlp_api::instruction_builder::delegate( payer.pubkey(), delegated.pubkey(), None, @@ -85,7 +85,7 @@ async fn test_delegation_confined_accounts_allows_system_validator() { let assign_res = banks.process_transaction(assign_tx).await; assert!(assign_res.is_ok()); - let ix = dlp::instruction_builder::delegate_with_any_validator( + let ix = dlp_api::instruction_builder::delegate_with_any_validator( payer.pubkey(), delegated.pubkey(), None, diff --git a/tests/test_finalize.rs b/tests/test_finalize.rs index 08cfe691..d4354fd7 100644 --- a/tests/test_finalize.rs +++ b/tests/test_finalize.rs @@ -52,7 +52,7 @@ async fn test_finalize() { let new_state_data_before_finalize = new_state_before_finalize.data.clone(); // Submit the finalize tx - let ix = dlp::instruction_builder::finalize( + let ix = dlp_api::instruction_builder::finalize( authority.pubkey(), DELEGATED_PDA_ID, ); diff --git a/tests/test_init_fees_vault.rs b/tests/test_init_fees_vault.rs index 48e2919e..6c479400 100644 --- a/tests/test_init_fees_vault.rs +++ b/tests/test_init_fees_vault.rs @@ -16,7 +16,8 @@ async fn test_init_fees_vault() { // Setup let (banks, payer, _, blockhash) = setup_program_test_env().await; - let ix = dlp::instruction_builder::init_protocol_fees_vault(payer.pubkey()); + let ix = + dlp_api::instruction_builder::init_protocol_fees_vault(payer.pubkey()); let tx = Transaction::new_signed_with_payer( &[ix], Some(&payer.pubkey()), diff --git a/tests/test_init_validator_fees_vault.rs b/tests/test_init_validator_fees_vault.rs index 3505ce48..67db1bb9 100644 --- a/tests/test_init_validator_fees_vault.rs +++ b/tests/test_init_validator_fees_vault.rs @@ -19,7 +19,7 @@ async fn test_init_validator_fees_vault() { let (banks, payer, admin, blockhash) = setup_program_test_env().await; let validator_identity = Pubkey::new_unique(); - let ix = dlp::instruction_builder::init_validator_fees_vault( + let ix = dlp_api::instruction_builder::init_validator_fees_vault( payer.pubkey(), admin.pubkey(), validator_identity, @@ -42,7 +42,7 @@ async fn test_init_validator_fees_vault() { // Assert record cannot be created if the admin is not the correct one let validator_identity = Pubkey::new_unique(); - let ix = dlp::instruction_builder::init_validator_fees_vault( + let ix = dlp_api::instruction_builder::init_validator_fees_vault( payer.pubkey(), payer.pubkey(), validator_identity, diff --git a/tests/test_lamports_settlement.rs b/tests/test_lamports_settlement.rs index d95a4ea7..4b476dd2 100644 --- a/tests/test_lamports_settlement.rs +++ b/tests/test_lamports_settlement.rs @@ -421,7 +421,7 @@ async fn undelegate(args: UndelegateArgs<'_>) { delegation_record_pda_from_delegated_account(&args.delegated_account); // Submit the undelegate tx - let ix = dlp::instruction_builder::undelegate( + let ix = dlp_api::instruction_builder::undelegate( args.authority.pubkey(), args.delegated_account, args.owner_program, @@ -470,7 +470,7 @@ struct FinalizeNewStateArgs<'a> { } async fn finalize_new_state(args: FinalizeNewStateArgs<'_>) { - let ix = dlp::instruction_builder::finalize( + let ix = dlp_api::instruction_builder::finalize( args.authority.pubkey(), args.delegated_account, ); @@ -516,7 +516,7 @@ async fn commit_new_state(args: CommitNewStateArgs<'_>) { }; // Commit the state for the delegated account - let ix = dlp::instruction_builder::commit_state( + let ix = dlp_api::instruction_builder::commit_state( args.authority.pubkey(), args.delegated_account, args.delegated_account_owner, diff --git a/tests/test_protocol_claim_fees.rs b/tests/test_protocol_claim_fees.rs index 152c508e..61ce1e2f 100644 --- a/tests/test_protocol_claim_fees.rs +++ b/tests/test_protocol_claim_fees.rs @@ -21,7 +21,7 @@ async fn test_protocol_claim_fees() { let fees_vault_pda = fees_vault_pda(); // Submit the claim fees tx - let ix = dlp::instruction_builder::protocol_claim_fees(admin.pubkey()); + let ix = dlp_api::instruction_builder::protocol_claim_fees(admin.pubkey()); let tx = Transaction::new_signed_with_payer( &[ix], Some(&payer.pubkey()), diff --git a/tests/test_top_up.rs b/tests/test_top_up.rs index c3e7db90..c6f644ab 100644 --- a/tests/test_top_up.rs +++ b/tests/test_top_up.rs @@ -31,7 +31,7 @@ async fn test_top_up_ephemeral_balance() { // Setup let (banks, payer, _, blockhash) = setup_program_test_env().await; - let ix = dlp::instruction_builder::top_up_ephemeral_balance( + let ix = dlp_api::instruction_builder::top_up_ephemeral_balance( payer.pubkey(), payer.pubkey(), None, @@ -66,7 +66,7 @@ async fn test_top_up_ephemeral_balance_for_pubkey() { let pubkey = Keypair::new().pubkey(); - let ix = dlp::instruction_builder::top_up_ephemeral_balance( + let ix = dlp_api::instruction_builder::top_up_ephemeral_balance( payer.pubkey(), pubkey, None, @@ -99,14 +99,14 @@ async fn test_top_up_ephemeral_balance_and_delegate() { let (banks, payer, _, blockhash) = setup_program_test_env().await; // Top-up Ix - let ix = dlp::instruction_builder::top_up_ephemeral_balance( + let ix = dlp_api::instruction_builder::top_up_ephemeral_balance( payer.pubkey(), payer.pubkey(), None, None, ); // Delegate ephemeral balance Ix - let delegate_ix = dlp::instruction_builder::delegate_ephemeral_balance( + let delegate_ix = dlp_api::instruction_builder::delegate_ephemeral_balance( payer.pubkey(), payer.pubkey(), DelegateEphemeralBalanceArgs::default(), @@ -158,14 +158,14 @@ async fn test_top_up_ephemeral_balance_and_delegate_for_pubkey() { let pubkey = key.pubkey(); // Top-up Ix - let ix = dlp::instruction_builder::top_up_ephemeral_balance( + let ix = dlp_api::instruction_builder::top_up_ephemeral_balance( payer.pubkey(), pubkey, None, None, ); // Delegate ephemeral balance Ix - let delegate_ix = dlp::instruction_builder::delegate_ephemeral_balance( + let delegate_ix = dlp_api::instruction_builder::delegate_ephemeral_balance( payer.pubkey(), pubkey, DelegateEphemeralBalanceArgs::default(), @@ -210,7 +210,7 @@ async fn test_undelegate() { assert_eq!(ephemeral_balance_owner, dlp::id()); // Undelegate ephemeral balance Ix - let ix = dlp::instruction_builder::undelegate( + let ix = dlp_api::instruction_builder::undelegate( validator.pubkey(), ephemeral_balance_pda, system_program::id(), @@ -260,14 +260,14 @@ async fn test_undelegate_and_close() { .lamports; // Undelegate ephemeral balance Ix - let ix = dlp::instruction_builder::undelegate( + let ix = dlp_api::instruction_builder::undelegate( validator.pubkey(), ephemeral_balance_pda, system_program::id(), validator.pubkey(), ); - let ix_close = dlp::instruction_builder::close_ephemeral_balance( + let ix_close = dlp_api::instruction_builder::close_ephemeral_balance( payer_alt.pubkey(), 0, ); diff --git a/tests/test_undelegate.rs b/tests/test_undelegate.rs index 0a26135c..54669b15 100644 --- a/tests/test_undelegate.rs +++ b/tests/test_undelegate.rs @@ -40,13 +40,13 @@ async fn test_finalize_and_undelegate() { let new_state_data_before_finalize = new_state_before_finalize.data.clone(); // Create the finalize tx - let ix_finalize = dlp::instruction_builder::finalize( + let ix_finalize = dlp_api::instruction_builder::finalize( authority.pubkey(), DELEGATED_PDA_ID, ); // Create the undelegate tx - let ix_undelegate = dlp::instruction_builder::undelegate( + let ix_undelegate = dlp_api::instruction_builder::undelegate( authority.pubkey(), DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, diff --git a/tests/test_undelegate_confined_account.rs b/tests/test_undelegate_confined_account.rs index 52021520..7724caa3 100644 --- a/tests/test_undelegate_confined_account.rs +++ b/tests/test_undelegate_confined_account.rs @@ -30,7 +30,7 @@ async fn test_undelegate_confined_account() { let data_before = delegated_before.data.clone(); // Submit the admin-only undelegate (confined) tx - let ix = dlp::instruction_builder::undelegate_confined_account( + let ix = dlp_api::instruction_builder::undelegate_confined_account( admin.pubkey(), DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, diff --git a/tests/test_undelegate_on_curve.rs b/tests/test_undelegate_on_curve.rs index cfa783ef..c6256c16 100644 --- a/tests/test_undelegate_on_curve.rs +++ b/tests/test_undelegate_on_curve.rs @@ -32,7 +32,7 @@ async fn test_undelegate_on_curve() { ); // Submit the undelegate tx - let ix = dlp::instruction_builder::undelegate( + let ix = dlp_api::instruction_builder::undelegate( validator.pubkey(), delegated_on_curve.pubkey(), system_program::id(), diff --git a/tests/test_undelegate_without_commit.rs b/tests/test_undelegate_without_commit.rs index 87a7a5d5..456c7e37 100644 --- a/tests/test_undelegate_without_commit.rs +++ b/tests/test_undelegate_without_commit.rs @@ -39,7 +39,7 @@ async fn test_undelegate_without_commit() { delegated_pda_state_before_undelegation.data.clone(); // Submit the undelegate tx - let ix = dlp::instruction_builder::undelegate( + let ix = dlp_api::instruction_builder::undelegate( validator.pubkey(), DELEGATED_PDA_ID, DELEGATED_PDA_OWNER_ID, diff --git a/tests/test_validator_claim_fees.rs b/tests/test_validator_claim_fees.rs index dc153eb6..e29d57e7 100644 --- a/tests/test_validator_claim_fees.rs +++ b/tests/test_validator_claim_fees.rs @@ -47,7 +47,7 @@ async fn test_validator_claim_fees() { // Submit the withdrawal tx let withdrawal_amount = 100000; - let ix = dlp::instruction_builder::validator_claim_fees( + let ix = dlp_api::instruction_builder::validator_claim_fees( validator.pubkey(), Some(withdrawal_amount), ); diff --git a/tests/test_whitelist_validator_for_program.rs b/tests/test_whitelist_validator_for_program.rs index 79b90750..80c64541 100644 --- a/tests/test_whitelist_validator_for_program.rs +++ b/tests/test_whitelist_validator_for_program.rs @@ -18,7 +18,7 @@ async fn test_whitelist_validator_for_program() { // Setup let (banks, _, validator, blockhash) = setup_program_test_env().await; - let ix = dlp::instruction_builder::whitelist_validator_for_program( + let ix = dlp_api::instruction_builder::whitelist_validator_for_program( validator.pubkey(), validator.pubkey(), DELEGATED_PDA_OWNER_ID, @@ -52,7 +52,7 @@ async fn test_remove_validator_for_program() { // Setup let (banks, _, validator, blockhash) = setup_program_test_env().await; - let ix = dlp::instruction_builder::whitelist_validator_for_program( + let ix = dlp_api::instruction_builder::whitelist_validator_for_program( validator.pubkey(), validator.pubkey(), DELEGATED_PDA_OWNER_ID, @@ -69,7 +69,7 @@ async fn test_remove_validator_for_program() { assert!(res.is_ok()); // Remove the validator - let ix = dlp::instruction_builder::whitelist_validator_for_program( + let ix = dlp_api::instruction_builder::whitelist_validator_for_program( validator.pubkey(), validator.pubkey(), DELEGATED_PDA_OWNER_ID,