diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d77bbb0f4..eb573c0cac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features +- program: add padding to swift messages ([#1845](https://github.com/drift-labs/protocol-v2/pull/1845)) - program: rm lp ([#1755](https://github.com/drift-labs/protocol-v2/pull/1755)) ### Fixes diff --git a/programs/drift/src/validation/sig_verification.rs b/programs/drift/src/validation/sig_verification.rs index 3349c7d5d7..da3893e66d 100644 --- a/programs/drift/src/validation/sig_verification.rs +++ b/programs/drift/src/validation/sig_verification.rs @@ -14,6 +14,9 @@ use solana_program::program_memory::sol_memcmp; use solana_program::sysvar; use std::convert::TryInto; +#[cfg(test)] +mod tests; + const ED25519_PROGRAM_INPUT_HEADER_LEN: usize = 2; const SIGNATURE_LEN: u16 = 64; @@ -45,6 +48,7 @@ pub struct Ed25519SignatureOffsets { pub message_instruction_index: u16, } +#[derive(Debug)] pub struct VerifiedMessage { pub signed_msg_order_params: OrderParams, pub sub_account_id: Option, @@ -60,6 +64,67 @@ fn slice_eq(a: &[u8], b: &[u8]) -> bool { a.len() == b.len() && sol_memcmp(a, b, a.len()) == 0 } +pub fn deserialize_into_verified_message( + payload: Vec, + signature: &[u8; 64], + is_delegate_signer: bool, +) -> Result { + if is_delegate_signer { + if payload.len() < 8 { + return Err(SignatureVerificationError::InvalidMessageDataSize.into()); + } + let min_len: usize = std::mem::size_of::(); + let mut owned = payload; + if owned.len() < min_len { + owned.resize(min_len, 0); + } + let deserialized = SignedMsgOrderParamsDelegateMessage::deserialize( + &mut &owned[8..], // 8 byte manual discriminator + ) + .map_err(|_| { + msg!("Invalid message encoding for is_delegate_signer = true"); + SignatureVerificationError::InvalidMessageDataSize + })?; + + return Ok(VerifiedMessage { + signed_msg_order_params: deserialized.signed_msg_order_params, + sub_account_id: None, + delegate_signed_taker_pubkey: Some(deserialized.taker_pubkey), + slot: deserialized.slot, + uuid: deserialized.uuid, + take_profit_order_params: deserialized.take_profit_order_params, + stop_loss_order_params: deserialized.stop_loss_order_params, + signature: *signature, + }); + } else { + if payload.len() < 8 { + return Err(SignatureVerificationError::InvalidMessageDataSize.into()); + } + let min_len: usize = std::mem::size_of::(); + let mut owned = payload; + if owned.len() < min_len { + owned.resize(min_len, 0); + } + let deserialized = SignedMsgOrderParamsMessage::deserialize( + &mut &owned[8..], // 8 byte manual discriminator + ) + .map_err(|_| { + msg!("Invalid delegate message encoding for with is_delegate_signer = false"); + SignatureVerificationError::InvalidMessageDataSize + })?; + return Ok(VerifiedMessage { + signed_msg_order_params: deserialized.signed_msg_order_params, + sub_account_id: Some(deserialized.sub_account_id), + delegate_signed_taker_pubkey: None, + slot: deserialized.slot, + uuid: deserialized.uuid, + take_profit_order_params: deserialized.take_profit_order_params, + stop_loss_order_params: deserialized.stop_loss_order_params, + signature: *signature, + }); + } +} + /// Check Ed25519Program instruction data verifies the given msg /// /// `ix` an Ed25519Program instruction [see](https://github.com/solana-labs/solana/blob/master/sdk/src/ed25519_instruction.rs)) @@ -232,45 +297,7 @@ pub fn verify_and_decode_ed25519_msg( let payload = hex::decode(payload).map_err(|_| SignatureVerificationError::InvalidMessageHex)?; - if is_delegate_signer { - let deserialized = SignedMsgOrderParamsDelegateMessage::deserialize( - &mut &payload[8..], // 8 byte manual discriminator - ) - .map_err(|_| { - msg!("Invalid message encoding for is_delegate_signer = true"); - SignatureVerificationError::InvalidMessageDataSize - })?; - - return Ok(VerifiedMessage { - signed_msg_order_params: deserialized.signed_msg_order_params, - sub_account_id: None, - delegate_signed_taker_pubkey: Some(deserialized.taker_pubkey), - slot: deserialized.slot, - uuid: deserialized.uuid, - take_profit_order_params: deserialized.take_profit_order_params, - stop_loss_order_params: deserialized.stop_loss_order_params, - signature: *signature, - }); - } else { - let deserialized = SignedMsgOrderParamsMessage::deserialize( - &mut &payload[8..], // 8 byte manual discriminator - ) - .map_err(|_| { - msg!("Invalid delegate message encoding for with is_delegate_signer = false"); - SignatureVerificationError::InvalidMessageDataSize - })?; - - return Ok(VerifiedMessage { - signed_msg_order_params: deserialized.signed_msg_order_params, - sub_account_id: Some(deserialized.sub_account_id), - delegate_signed_taker_pubkey: None, - slot: deserialized.slot, - uuid: deserialized.uuid, - take_profit_order_params: deserialized.take_profit_order_params, - stop_loss_order_params: deserialized.stop_loss_order_params, - signature: *signature, - }); - } + deserialize_into_verified_message(payload, signature, is_delegate_signer) } #[error_code] diff --git a/programs/drift/src/validation/sig_verification/tests.rs b/programs/drift/src/validation/sig_verification/tests.rs new file mode 100644 index 0000000000..e0f941d883 --- /dev/null +++ b/programs/drift/src/validation/sig_verification/tests.rs @@ -0,0 +1,184 @@ +mod sig_verification { + use std::str::FromStr; + + use anchor_lang::prelude::Pubkey; + + use crate::controller::position::PositionDirection; + use crate::validation::sig_verification::deserialize_into_verified_message; + + #[test] + fn test_deserialize_into_verified_message_non_delegate() { + let signature = [1u8; 64]; + let payload = vec![ + 200, 213, 166, 94, 34, 52, 245, 93, 0, 1, 0, 1, 0, 202, 154, 59, 0, 0, 0, 0, 0, 248, + 89, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 10, 1, 192, 181, 74, 13, 0, 0, 0, 0, + 1, 0, 248, 89, 13, 0, 0, 0, 0, 0, 0, 232, 3, 0, 0, 0, 0, 0, 0, 72, 112, 54, 84, 106, + 83, 48, 107 + ]; + + // Test deserialization with non-delegate signer + let result = deserialize_into_verified_message(payload, &signature, false); + assert!(result.is_ok()); + + let verified_message = result.unwrap(); + + // Verify the deserialized message has expected structure + assert_eq!(verified_message.signature, signature); + assert_eq!(verified_message.sub_account_id, Some(0)); + assert_eq!(verified_message.delegate_signed_taker_pubkey, None); + assert_eq!(verified_message.slot, 1000); + assert_eq!(verified_message.uuid, [72, 112, 54, 84, 106, 83, 48, 107]); + assert!(verified_message.take_profit_order_params.is_none()); + assert!(verified_message.stop_loss_order_params.is_none()); + // Verify order params + let order_params = &verified_message.signed_msg_order_params; + assert_eq!(order_params.user_order_id, 1); + assert_eq!(order_params.direction, PositionDirection::Long); + assert_eq!(order_params.base_asset_amount, 1000000000u64); + assert_eq!(order_params.price, 224000000u64); + assert_eq!(order_params.market_index, 0); + assert_eq!(order_params.reduce_only, false); + assert_eq!(order_params.auction_duration, Some(10)); + assert_eq!(order_params.auction_start_price, Some(223000000i64)); + assert_eq!(order_params.auction_end_price, Some(224000000i64)); + } + + #[test] + fn test_deserialize_into_verified_message_non_delegate_with_tpsl() { + let signature = [1u8; 64]; + let payload = vec![ + 200, 213, 166, 94, 34, 52, 245, 93, 0, 1, 0, 3, 0, 96, 254, 205, 0, 0, 0, 0, 64, 85, + 32, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 10, 1, 128, 133, 181, 13, 0, 0, 0, 0, + 1, 64, 85, 32, 14, 0, 0, 0, 0, 2, 0, 41, 9, 0, 0, 0, 0, 0, 0, 67, 82, 79, 51, 105, 114, + 71, 49, 1, 0, 28, 78, 14, 0, 0, 0, 0, 0, 96, 254, 205, 0, 0, 0, 0, 1, 64, 58, 105, 13, + 0, 0, 0, 0, 0, 96, 254, 205 + ]; + + // Test deserialization with delegate signer + let result = deserialize_into_verified_message(payload, &signature, false); + assert!(result.is_ok()); + + let verified_message = result.unwrap(); + + // Verify the deserialized message has expected structure + assert_eq!(verified_message.signature, signature); + assert_eq!(verified_message.sub_account_id, Some(2)); + assert_eq!(verified_message.delegate_signed_taker_pubkey, None); + assert_eq!(verified_message.slot, 2345); + assert_eq!(verified_message.uuid, [67, 82, 79, 51, 105, 114, 71, 49]); + assert!(verified_message.take_profit_order_params.is_some()); + let tp = verified_message.take_profit_order_params.unwrap(); + assert_eq!(tp.base_asset_amount, 3456000000u64); + assert_eq!(tp.trigger_price, 240000000u64); + + assert!(verified_message.stop_loss_order_params.is_some()); + let sl = verified_message.stop_loss_order_params.unwrap(); + assert_eq!(sl.base_asset_amount, 3456000000u64); + assert_eq!(sl.trigger_price, 225000000u64); + + // Verify order params + let order_params = &verified_message.signed_msg_order_params; + assert_eq!(order_params.user_order_id, 3); + assert_eq!(order_params.direction, PositionDirection::Long); + assert_eq!(order_params.base_asset_amount, 3456000000u64); + assert_eq!(order_params.price, 237000000u64); + assert_eq!(order_params.market_index, 0); + assert_eq!(order_params.reduce_only, false); + assert_eq!(order_params.auction_duration, Some(10)); + assert_eq!(order_params.auction_start_price, Some(230000000i64)); + assert_eq!(order_params.auction_end_price, Some(237000000i64)); + } + + #[test] + fn test_deserialize_into_verified_message_delegate() { + let signature = [1u8; 64]; + let payload = vec![ + 66, 101, 102, 56, 199, 37, 158, 35, 0, 1, 1, 2, 0, 202, 154, 59, 0, 0, 0, 0, 64, 85, + 32, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 10, 1, 0, 28, 78, 14, 0, 0, 0, 0, 1, + 128, 151, 47, 14, 0, 0, 0, 0, 242, 208, 117, 159, 92, 135, 34, 224, 147, 14, 64, 92, 7, + 25, 145, 237, 79, 35, 72, 24, 140, 13, 25, 189, 134, 243, 232, 5, 89, 37, 166, 242, 41, + 9, 0, 0, 0, 0, 0, 0, 67, 82, 79, 51, 105, 114, 71, 49 + ]; + + // Test deserialization with delegate signer + let result = deserialize_into_verified_message(payload, &signature, true); + assert!(result.is_ok()); + + let verified_message = result.unwrap(); + + // Verify the deserialized message has expected structure + assert_eq!(verified_message.signature, signature); + assert_eq!(verified_message.sub_account_id, None); + assert_eq!( + verified_message.delegate_signed_taker_pubkey, + Some(Pubkey::from_str("HLr2UfL422cakKkaBG4z1bMZrcyhmzX2pHdegjM6fYXB").unwrap()) + ); + assert_eq!(verified_message.slot, 2345); + assert_eq!(verified_message.uuid, [67, 82, 79, 51, 105, 114, 71, 49]); + assert!(verified_message.take_profit_order_params.is_none()); + assert!(verified_message.stop_loss_order_params.is_none()); + + // Verify order params + let order_params = &verified_message.signed_msg_order_params; + assert_eq!(order_params.user_order_id, 2); + assert_eq!(order_params.direction, PositionDirection::Short); + assert_eq!(order_params.base_asset_amount, 1000000000u64); + assert_eq!(order_params.price, 237000000u64); + assert_eq!(order_params.market_index, 0); + assert_eq!(order_params.reduce_only, false); + assert_eq!(order_params.auction_duration, Some(10)); + assert_eq!(order_params.auction_start_price, Some(240000000i64)); + assert_eq!(order_params.auction_end_price, Some(238000000i64)); + } + + #[test] + fn test_deserialize_into_verified_message_delegate_with_tpsl() { + let signature = [1u8; 64]; + let payload = vec![ + 66, 101, 102, 56, 199, 37, 158, 35, 0, 1, 1, 2, 0, 202, 154, 59, 0, 0, 0, 0, 64, 85, + 32, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 10, 1, 0, 28, 78, 14, 0, 0, 0, 0, 1, + 128, 151, 47, 14, 0, 0, 0, 0, 241, 148, 164, 10, 232, 65, 33, 157, 18, 12, 251, 132, + 245, 208, 37, 127, 112, 55, 83, 186, 54, 139, 1, 135, 220, 180, 208, 219, 189, 94, 79, + 148, 41, 9, 0, 0, 0, 0, 0, 0, 67, 82, 79, 51, 105, 114, 71, 49, 1, 128, 133, 181, 13, + 0, 0, 0, 0, 0, 202, 154, 59, 0, 0, 0, 0, 1, 128, 178, 230, 14, 0, 0, 0, 0, 0, 202, 154, + 59 + ]; + + // Test deserialization with delegate signer + let result = deserialize_into_verified_message(payload, &signature, true); + assert!(result.is_ok()); + + let verified_message = result.unwrap(); + + // Verify the deserialized message has expected structure + assert_eq!(verified_message.signature, signature); + assert_eq!(verified_message.sub_account_id, None); + assert_eq!( + verified_message.delegate_signed_taker_pubkey, + Some(Pubkey::from_str("HG2iQKnRkkasrLptwMZewV6wT7KPstw9wkA8yyu8Nx3m").unwrap()) + ); + assert_eq!(verified_message.slot, 2345); + assert_eq!(verified_message.uuid, [67, 82, 79, 51, 105, 114, 71, 49]); + assert!(verified_message.take_profit_order_params.is_some()); + let tp = verified_message.take_profit_order_params.unwrap(); + assert_eq!(tp.base_asset_amount, 1000000000u64); + assert_eq!(tp.trigger_price, 230000000u64); + + assert!(verified_message.stop_loss_order_params.is_some()); + let sl = verified_message.stop_loss_order_params.unwrap(); + assert_eq!(sl.base_asset_amount, 1000000000u64); + assert_eq!(sl.trigger_price, 250000000u64); + + // Verify order params + let order_params = &verified_message.signed_msg_order_params; + assert_eq!(order_params.user_order_id, 2); + assert_eq!(order_params.direction, PositionDirection::Short); + assert_eq!(order_params.base_asset_amount, 1000000000u64); + assert_eq!(order_params.price, 237000000u64); + assert_eq!(order_params.market_index, 0); + assert_eq!(order_params.reduce_only, false); + assert_eq!(order_params.auction_duration, Some(10)); + assert_eq!(order_params.auction_start_price, Some(240000000i64)); + assert_eq!(order_params.auction_end_price, Some(238000000i64)); + } +}