Skip to content

Commit

Permalink
Nour/resize swift ed25519 (#1405)
Browse files Browse the repository at this point in the history
* bankrun tests pass

* everything but one local val test

* all tests pass

* Update programs/drift/src/validation/sig_verification.rs

Co-authored-by: jordy25519 <[email protected]>

* Update programs/drift/src/validation/sig_verification.rs

Co-authored-by: jordy25519 <[email protected]>

* fix build

---------

Co-authored-by: jordy25519 <[email protected]>
  • Loading branch information
NourAlharithi and jordy25519 authored Dec 30, 2024
1 parent 7a19803 commit ef484d5
Show file tree
Hide file tree
Showing 9 changed files with 377 additions and 310 deletions.
25 changes: 13 additions & 12 deletions programs/drift/src/instructions/keeper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,11 @@ use crate::state::user::{
MarginMode, MarketType, OrderStatus, OrderTriggerCondition, OrderType, User, UserStats,
};
use crate::state::user_map::{load_user_map, load_user_maps, UserMap, UserStatsMap};
use crate::validation::sig_verification::{extract_ed25519_ix_signature, verify_ed25519_msg};
use crate::validation::sig_verification::verify_ed25519_msg;
use crate::validation::user::{validate_user_deletion, validate_user_is_idle};
use crate::{
controller, digest_struct, digest_struct_hex, load, math, print_error, safe_decrement,
OracleSource, GOV_SPOT_MARKET_INDEX, MARGIN_PRECISION,
controller, load, math, print_error, safe_decrement, OracleSource, GOV_SPOT_MARKET_INDEX,
MARGIN_PRECISION,
};
use crate::{load_mut, QUOTE_PRECISION_U64};
use crate::{validate, QUOTE_PRECISION_I128};
Expand Down Expand Up @@ -603,9 +603,6 @@ pub fn handle_place_swift_taker_order<'c: 'info, 'info>(
ctx: Context<'_, '_, 'c, 'info, PlaceSwiftTakerOrder<'info>>,
swift_order_params_message_bytes: Vec<u8>,
) -> Result<()> {
let taker_order_params_message: SwiftOrderParamsMessage =
SwiftOrderParamsMessage::deserialize(&mut &swift_order_params_message_bytes[..]).unwrap();

let state = &ctx.accounts.state;

// TODO: generalize to support multiple market types
Expand All @@ -629,7 +626,7 @@ pub fn handle_place_swift_taker_order<'c: 'info, 'info>(
taker_key,
&mut taker,
&mut swift_taker,
taker_order_params_message,
swift_order_params_message_bytes,
&ctx.accounts.ix_sysvar.to_account_info(),
&perp_market_map,
&spot_market_map,
Expand All @@ -643,7 +640,7 @@ pub fn place_swift_taker_order<'c: 'info, 'info>(
taker_key: Pubkey,
taker: &mut RefMut<User>,
swift_account: &mut SwiftUserOrdersZeroCopyMut,
taker_order_params_message: SwiftOrderParamsMessage,
taker_order_params_message_bytes: Vec<u8>,
ix_sysvar: &AccountInfo<'info>,
perp_market_map: &PerpMarketMap,
spot_market_map: &SpotMarketMap,
Expand All @@ -664,15 +661,19 @@ pub fn place_swift_taker_order<'c: 'info, 'info>(
)?;

// Verify data from verify ix
let digest_hex = digest_struct_hex!(taker_order_params_message);
let ix: Instruction = load_instruction_at_checked(ix_idx as usize - 1, ix_sysvar)?;
verify_ed25519_msg(
let verified_message_and_signature = verify_ed25519_msg(
&ix,
ix_idx,
&taker.authority.to_bytes(),
arrayref::array_ref!(digest_hex, 0, 64),
&taker_order_params_message_bytes[..],
12,
)?;

let signature = extract_ed25519_ix_signature(&ix.data)?;
let taker_order_params_message: SwiftOrderParamsMessage =
verified_message_and_signature.swift_order_params_message;

let signature = verified_message_and_signature.signature;
let clock = &Clock::get()?;

// First order must be a taker order
Expand Down
222 changes: 168 additions & 54 deletions programs/drift/src/validation/sig_verification.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,45 @@
use crate::error::ErrorCode;
use crate::state::order_params::SwiftOrderParamsMessage;
use anchor_lang::prelude::*;
use bytemuck::try_cast_slice;
use bytemuck::{Pod, Zeroable};
use byteorder::ByteOrder;
use byteorder::LE;
use solana_program::ed25519_program::ID as ED25519_ID;
use solana_program::instruction::Instruction;
use std::convert::TryInto;

const ED25519_PROGRAM_INPUT_HEADER_LEN: usize = 2;

const SIGNATURE_LEN: u16 = 64;
const PUBKEY_LEN: u16 = 32;
const MESSAGE_SIZE_LEN: u16 = 2;

/// Part of the inputs to the built-in `ed25519_program` on Solana that represents a single
/// signature verification request.
///
/// `ed25519_program` does not receive the signature data directly. Instead, it receives
/// these fields that indicate the location of the signature data within data of other
/// instructions within the same transaction.
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
#[repr(C)]
pub struct Ed25519SignatureOffsets {
/// Offset to the ed25519 signature within the instruction data.
pub signature_offset: u16,
/// Index of the instruction that contains the signature.
pub signature_instruction_index: u16,
/// Offset to the public key within the instruction data.
pub public_key_offset: u16,
/// Index of the instruction that contains the public key.
pub public_key_instruction_index: u16,
/// Offset to the signed payload within the instruction data.
pub message_data_offset: u16,
// Size of the signed payload.
pub message_data_size: u16,
/// Index of the instruction that contains the signed payload.
pub message_instruction_index: u16,
}

/// Verify Ed25519Program instruction fields
pub fn verify_ed25519_ix(ix: &Instruction, pubkey: &[u8], msg: &[u8], sig: &[u8]) -> Result<()> {
if ix.program_id != ED25519_ID || // The program id we expect
Expand Down Expand Up @@ -79,6 +115,11 @@ fn check_ed25519_data(data: &[u8], pubkey: &[u8], msg: &[u8], sig: &[u8]) -> Res
Ok(())
}

pub struct VerifiedMessage {
pub swift_order_params_message: SwiftOrderParamsMessage,
pub signature: [u8; 64],
}

/// 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))
Expand All @@ -87,11 +128,13 @@ fn check_ed25519_data(data: &[u8], pubkey: &[u8], msg: &[u8], sig: &[u8]) -> Res
///
/// `pubkey` expected pubkey of the signer
///
pub fn verify_ed25519_msg<const N: usize>(
pub fn verify_ed25519_msg(
ix: &Instruction,
pubkey: &[u8; 32],
msg: &[u8; N],
) -> Result<()> {
current_ix_index: u16,
signer: &[u8; 32],
msg: &[u8],
message_offset: u16,
) -> Result<VerifiedMessage> {
if ix.program_id != ED25519_ID || ix.accounts.len() != 0 {
msg!("Invalid Ix: program ID: {:?}", ix.program_id);
msg!("Invalid Ix: accounts: {:?}", ix.accounts.len());
Expand All @@ -100,72 +143,143 @@ pub fn verify_ed25519_msg<const N: usize>(

let ix_data = &ix.data;
// According to this layout used by the Ed25519Program]
if ix_data.len() <= 112 {
if ix_data.len() < 2 {
msg!(
"Invalid Ix: data: {:?}, len: {:?}",
"Invalid Ix, should be header len = 2. data: {:?}",
ix.data.len(),
16 + 64 + 32 + N
);
return Err(ErrorCode::SigVerificationFailed.into());
return Err(SignatureVerificationError::InvalidEd25519InstructionDataLength.into());
}

// Check the ed25519 verify ix header is sound
let num_signatures = ix_data[0];
let padding = ix_data[1];
let signature_offset = u16::from_le_bytes(ix_data[2..=3].try_into().unwrap());
let signature_instruction_index = u16::from_le_bytes(ix_data[4..=5].try_into().unwrap());
let public_key_offset = u16::from_le_bytes(ix_data[6..=7].try_into().unwrap());
let public_key_instruction_index = u16::from_le_bytes(ix_data[8..=9].try_into().unwrap());
let message_data_offset = u16::from_le_bytes(ix_data[10..=11].try_into().unwrap());
let message_data_size = u16::from_le_bytes(ix_data[12..=13].try_into().unwrap());
let message_instruction_index = u16::from_le_bytes(ix_data[14..=15].try_into().unwrap());
// Parse the ix data into the offsets
let args: &[Ed25519SignatureOffsets] =
try_cast_slice(&ix_data[ED25519_PROGRAM_INPUT_HEADER_LEN..]).map_err(|_| {
msg!("Invalid Ix: failed to cast slice");
ErrorCode::SigVerificationFailed
})?;

// Expected values
let exp_public_key_offset: u16 = 16;
let exp_signature_offset: u16 = exp_public_key_offset + 32_u16;
let exp_message_data_offset: u16 = exp_signature_offset + 64_u16;
let exp_num_signatures: u8 = 1;

// Header
if num_signatures != exp_num_signatures
|| padding != 0
|| signature_offset != exp_signature_offset
|| signature_instruction_index != u16::MAX
|| public_key_offset != exp_public_key_offset
|| public_key_instruction_index != u16::MAX
|| message_data_offset != exp_message_data_offset
|| message_instruction_index != u16::MAX
{
let offsets = &args[0];
if offsets.signature_offset != message_offset {
msg!(
"Invalid Ix: signature offset: {:?}",
offsets.signature_offset
);
return Err(ErrorCode::SigVerificationFailed.into());
}

// verify data is for digest and pubkey
let ix_msg_data = &ix_data[112..];
if ix_msg_data != msg || message_data_size != N as u16 {
let expected_public_key_offset = message_offset
.checked_add(SIGNATURE_LEN)
.ok_or(ErrorCode::SigVerificationFailed)?;
if offsets.public_key_offset != expected_public_key_offset {
msg!(
"Invalid Ix: public key offset: {:?}, expected: {:?}",
offsets.public_key_offset,
expected_public_key_offset
);
return Err(ErrorCode::SigVerificationFailed.into());
}

let ix_pubkey = &ix_data[16..16 + 32];
if ix_pubkey != pubkey {
msg!("Invalid Ix: pubkey: {:?}", ix_pubkey);
msg!("Invalid Ix: expected pubkey: {:?}", pubkey);
return Err(ErrorCode::SigVerificationFailed.into());
let expected_message_size_offset = expected_public_key_offset
.checked_add(PUBKEY_LEN)
.ok_or(ErrorCode::SigVerificationFailed)?;

let expected_message_data_offset = expected_message_size_offset
.checked_add(MESSAGE_SIZE_LEN)
.ok_or(SignatureVerificationError::MessageOffsetOverflow)?;
if offsets.message_data_offset != expected_message_data_offset {
return Err(SignatureVerificationError::InvalidMessageOffset.into());
}

Ok(())
}
let expected_message_size: u16 = {
let start = usize::from(
expected_message_size_offset
.checked_sub(message_offset)
.unwrap(),
);
let end = usize::from(
expected_message_data_offset
.checked_sub(message_offset)
.unwrap(),
);
LE::read_u16(&msg[start..end])
};
if offsets.message_data_size != expected_message_size {
return Err(SignatureVerificationError::InvalidMessageDataSize.into());
}
if offsets.signature_instruction_index != current_ix_index
|| offsets.public_key_instruction_index != current_ix_index
|| offsets.message_instruction_index != current_ix_index
{
return Err(SignatureVerificationError::InvalidInstructionIndex.into());
}

/// Extract pubkey from serialized Ed25519Program instruction data
pub fn extract_ed25519_ix_pubkey(ix_data: &[u8]) -> Result<[u8; 32]> {
match ix_data[16..16 + 32].try_into() {
Ok(raw) => Ok(raw),
Err(_) => Err(ErrorCode::SigVerificationFailed.into()),
let public_key = {
let start = usize::from(
expected_public_key_offset
.checked_sub(message_offset)
.unwrap(),
);
let end = start
.checked_add(anchor_lang::solana_program::pubkey::PUBKEY_BYTES)
.ok_or(SignatureVerificationError::MessageOffsetOverflow)?;
&msg[start..end]
};
let mut payload = {
let start = usize::from(
expected_message_data_offset
.checked_sub(message_offset)
.unwrap(),
);
let end = start
.checked_add(expected_message_size.into())
.ok_or(SignatureVerificationError::MessageOffsetOverflow)?;
&msg[start..end]
};

if public_key != signer {
msg!("Invalid Ix: message signed by: {:?}", public_key);
msg!("Invalid Ix: expected pubkey: {:?}", signer);
return Err(ErrorCode::SigVerificationFailed.into());
}

let signature = {
let start = usize::from(
offsets
.signature_offset
.checked_sub(message_offset)
.unwrap(),
);
let end = start
.checked_add(SIGNATURE_LEN.into())
.ok_or(SignatureVerificationError::InvalidSignatureOffset)?;
&msg[start..end].try_into().unwrap()
};

Ok(VerifiedMessage {
swift_order_params_message: SwiftOrderParamsMessage::deserialize(&mut payload).unwrap(),
signature: *signature,
})
}

pub fn extract_ed25519_ix_signature(ix_data: &[u8]) -> Result<[u8; 64]> {
match ix_data[48..48 + 64].try_into() {
Ok(raw) => Ok(raw),
Err(_) => Err(ErrorCode::SigVerificationFailed.into()),
}
#[error_code]
#[derive(PartialEq, Eq)]
pub enum SignatureVerificationError {
#[msg("invalid ed25519 instruction program")]
InvalidEd25519InstructionProgramId,
#[msg("invalid ed25519 instruction data length")]
InvalidEd25519InstructionDataLength,
#[msg("invalid signature index")]
InvalidSignatureIndex,
#[msg("invalid signature offset")]
InvalidSignatureOffset,
#[msg("invalid public key offset")]
InvalidPublicKeyOffset,
#[msg("invalid message offset")]
InvalidMessageOffset,
#[msg("invalid message data size")]
InvalidMessageDataSize,
#[msg("invalid instruction index")]
InvalidInstructionIndex,
#[msg("message offset overflow")]
MessageOffsetOverflow,
}
Loading

0 comments on commit ef484d5

Please sign in to comment.