Skip to content

Commit 776a6f7

Browse files
Merge pull request #18 from lazor-kit/audit-contract
Audit contract
2 parents b3f2362 + ce43a9c commit 776a6f7

26 files changed

Lines changed: 486 additions & 1423 deletions

‎Cargo.lock‎

Lines changed: 98 additions & 269 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎programs/default_policy/src/state.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
use anchor_lang::prelude::*;
1+
use anchor_lang::{prelude::*, solana_program::hash::HASH_BYTES};
22
use lazorkit::constants::PASSKEY_PUBLIC_KEY_SIZE;
33

44
#[derive(Debug, AnchorSerialize, AnchorDeserialize, PartialEq, Eq, Clone, Copy)]
55
pub struct DeviceSlot {
66
pub passkey_pubkey: [u8; PASSKEY_PUBLIC_KEY_SIZE],
7-
pub credential_hash: [u8; 32],
7+
pub credential_hash: [u8; HASH_BYTES],
88
}
99

1010
#[derive(Debug, AnchorSerialize, AnchorDeserialize)]

‎programs/lazorkit/Cargo.toml‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,4 @@ idl-build = ["anchor-lang/idl-build"]
2222
anchor-lang = { version = "0.31.0", features = ["init-if-needed"] }
2323
serde_json = "1.0.140"
2424
base64 = { version = "0.21.0", default-features = false, features = ["alloc"] }
25-
solana-secp256r1-program = "3.0.0"
2625

‎programs/lazorkit/src/constants.rs‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
use anchor_lang::prelude::*;
2-
use solana_secp256r1_program::COMPRESSED_PUBKEY_SERIALIZED_SIZE;
2+
3+
// Constants from solana-secp256r1-program (defined locally to avoid crate compilation issues)
4+
pub const COMPRESSED_PUBKEY_SERIALIZED_SIZE: usize = 33;
5+
pub const SIGNATURE_SERIALIZED_SIZE: usize = 64;
6+
pub const SIGNATURE_OFFSETS_SERIALIZED_SIZE: usize = 14;
7+
pub const SIGNATURE_OFFSETS_START: usize = 2;
8+
pub const DATA_START: usize = SIGNATURE_OFFSETS_SERIALIZED_SIZE + SIGNATURE_OFFSETS_START;
39

410
/// Solana's built-in Secp256r1 signature verification program ID
5-
pub const SECP256R1_PROGRAM_ID: Pubkey = Pubkey::new_from_array(solana_secp256r1_program::ID.to_bytes());
11+
/// This is the program ID for the secp256r1 native program
12+
pub const SECP256R1_PROGRAM_ID: Pubkey = anchor_lang::solana_program::pubkey!("Secp256r1SigVerify1111111111111111111111111");
613

714
/// Seed used for smart wallet PDA derivation
815
pub const SMART_WALLET_SEED: &[u8] = b"smart_wallet";

‎programs/lazorkit/src/instructions/create_smart_wallet.rs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use anchor_lang::{
22
prelude::*,
3+
solana_program::hash::HASH_BYTES,
34
system_program::{transfer, Transfer},
45
};
56

@@ -14,7 +15,7 @@ use crate::{
1415
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
1516
pub struct CreateSmartWalletArgs {
1617
pub passkey_public_key: [u8; PASSKEY_PUBLIC_KEY_SIZE],
17-
pub credential_hash: [u8; 32],
18+
pub credential_hash: [u8; HASH_BYTES],
1819
pub init_policy_data: Vec<u8>,
1920
pub wallet_id: u64,
2021
pub amount: u64,

‎programs/lazorkit/src/instructions/execute/chunk/close_chunk.rs‎

Lines changed: 0 additions & 59 deletions
This file was deleted.

‎programs/lazorkit/src/instructions/execute/chunk/create_chunk.rs‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,9 @@ pub fn create_chunk(ctx: Context<CreateChunk>, args: CreateChunkArgs) -> Result<
6969
owner_wallet_address: smart_wallet_key,
7070
cpi_hash: args.cpi_hash,
7171
authorized_nonce: last_nonce,
72-
authorized_timestamp: args.timestamp,
72+
authorized_timestamp: Clock::get()?.unix_timestamp,
7373
rent_refund_address: payer_key,
7474
});
75-
76-
ctx.accounts.wallet_state.last_nonce = validation::safe_increment_nonce(last_nonce);
7775
Ok(())
7876
}
7977

@@ -91,7 +89,6 @@ pub struct CreateChunk<'info> {
9189
pub smart_wallet: SystemAccount<'info>,
9290

9391
#[account(
94-
mut,
9592
seeds = [WalletState::PREFIX_SEED, smart_wallet.key().as_ref()],
9693
bump,
9794
owner = ID,

‎programs/lazorkit/src/instructions/execute/chunk/execute_chunk.rs‎

Lines changed: 85 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
use anchor_lang::prelude::*;
22

3-
use crate::error::LazorKitError;
3+
use crate::security::validation;
44
use crate::state::{Chunk, WalletState};
55
use crate::utils::{execute_cpi, PdaSigner};
66
use crate::{constants::SMART_WALLET_SEED, ID};
77
use anchor_lang::solana_program::hash::{HASH_BYTES, Hasher, hash};
88

99
/// Execute a previously created chunk
10+
/// Always returns Ok(()) - errors are logged but don't fail the transaction
1011
pub fn execute_chunk(
1112
ctx: Context<ExecuteChunk>,
1213
instruction_data_list: Vec<Vec<u8>>,
@@ -21,39 +22,68 @@ pub fn execute_chunk(
2122
let authorized_timestamp = chunk.authorized_timestamp;
2223
let expected_cpi_hash = chunk.cpi_hash;
2324

24-
let now = Clock::get()?.unix_timestamp;
25-
let session_end = authorized_timestamp + crate::security::MAX_SESSION_TTL_SECONDS;
26-
require!(
27-
now >= authorized_timestamp && now <= session_end,
28-
LazorKitError::TransactionTooOld
29-
);
30-
require!(
31-
chunk.owner_wallet_address == smart_wallet_key,
32-
LazorKitError::InvalidAccountOwner
33-
);
34-
35-
require!(
36-
!instruction_data_list.is_empty(),
37-
LazorKitError::InsufficientCpiAccounts
38-
);
39-
require!(
40-
instruction_data_list.len() == split_index.len() + 1,
41-
LazorKitError::InvalidInstructionData
42-
);
43-
44-
let instruction_count: u32 = instruction_data_list.len().try_into()
45-
.map_err(|_| LazorKitError::InvalidInstructionData)?;
25+
// Validate owner first (fail fast)
26+
if chunk.owner_wallet_address != smart_wallet_key {
27+
msg!("InvalidAccountOwner: Invalid account owner: expected={}, got={}", smart_wallet_key, chunk.owner_wallet_address);
28+
return Ok(());
29+
}
30+
31+
// Get current timestamp
32+
let now = match Clock::get() {
33+
Ok(clock) => clock.unix_timestamp,
34+
Err(e) => {
35+
msg!("InvalidInstruction: Error getting clock: {:?}", e);
36+
return Ok(());
37+
}
38+
};
39+
40+
// Validate timestamp - check if chunk is expired
41+
let session_end = authorized_timestamp + crate::security::MAX_SESSION_TTL_SECONDS * 2;
42+
if now > session_end {
43+
msg!("TransactionTooOld: Chunk expired: now={}, session_end={}", now, session_end);
44+
// Chunk will be closed automatically due to close = session_refund constraint
45+
return Ok(());
46+
}
47+
48+
// Validate instruction data
49+
if instruction_data_list.is_empty() {
50+
msg!("InvalidInstructionData: Instruction data list is empty");
51+
return Ok(());
52+
}
53+
54+
if instruction_data_list.len() != split_index.len() + 1 {
55+
msg!("InvalidInstructionData: Invalid instruction data: instruction_data_list.len()={}, split_index.len()={}",
56+
instruction_data_list.len(), split_index.len());
57+
return Ok(());
58+
}
59+
60+
// Serialize CPI data
61+
let instruction_count: u32 = match instruction_data_list.len().try_into() {
62+
Ok(count) => count,
63+
Err(e) => {
64+
msg!("InvalidInstructionData: Failed to convert instruction count: {:?}", e);
65+
return Ok(());
66+
}
67+
};
68+
4669
let mut serialized_cpi_data = Vec::new();
4770
serialized_cpi_data.extend_from_slice(&instruction_count.to_le_bytes());
71+
4872
for instruction_data in &instruction_data_list {
49-
let data_len: u32 = instruction_data.len().try_into()
50-
.map_err(|_| LazorKitError::InvalidInstructionData)?;
73+
let data_len: u32 = match instruction_data.len().try_into() {
74+
Ok(len) => len,
75+
Err(e) => {
76+
msg!("InvalidInstructionData: Failed to convert instruction data length: {:?}", e);
77+
return Ok(());
78+
}
79+
};
5180
serialized_cpi_data.extend_from_slice(&data_len.to_le_bytes());
5281
serialized_cpi_data.extend_from_slice(instruction_data);
5382
}
5483

5584
let cpi_data_hash = hash(&serialized_cpi_data).to_bytes();
5685

86+
// Hash CPI accounts
5787
let mut rh = Hasher::default();
5888
for account in cpi_accounts.iter() {
5989
rh.hash(account.key().as_ref());
@@ -67,25 +97,48 @@ pub fn execute_chunk(
6797
cpi_combined[HASH_BYTES..].copy_from_slice(&cpi_accounts_hash);
6898
let cpi_hash = hash(&cpi_combined).to_bytes();
6999

70-
require!(cpi_hash == expected_cpi_hash, LazorKitError::HashMismatch);
100+
// Validate hash
101+
if cpi_hash != expected_cpi_hash {
102+
msg!("HashMismatch: Hash mismatch: expected={:?}, got={:?}", expected_cpi_hash, cpi_hash);
103+
return Ok(());
104+
}
105+
106+
// Calculate account ranges
107+
let account_ranges = match crate::utils::calculate_account_ranges(cpi_accounts, &split_index) {
108+
Ok(ranges) => ranges,
109+
Err(e) => {
110+
msg!("InvalidInstructionData: Failed to calculate account ranges: {:?}", e);
111+
return Ok(());
112+
}
113+
};
71114

72-
let account_ranges = crate::utils::calculate_account_ranges(cpi_accounts, &split_index)?;
73-
crate::utils::validate_programs_in_ranges(cpi_accounts, &account_ranges)?;
115+
// Validate programs in ranges
116+
if let Err(e) = crate::utils::validate_programs_in_ranges(cpi_accounts, &account_ranges) {
117+
msg!("InvalidInstruction: Failed to validate programs in ranges: {:?}", e);
118+
return Ok(());
119+
}
74120

121+
// Execute CPI instructions
75122
let wallet_signer = PdaSigner {
76123
seeds: vec![SMART_WALLET_SEED.to_vec(), wallet_id.to_le_bytes().to_vec()],
77124
bump: wallet_bump,
78125
};
79126

80-
for (cpi_data, &(range_start, range_end)) in
81-
instruction_data_list.iter().zip(account_ranges.iter())
127+
for (idx, (cpi_data, &(range_start, range_end))) in
128+
instruction_data_list.iter().zip(account_ranges.iter()).enumerate()
82129
{
83130
let instruction_accounts = &cpi_accounts[range_start..range_end];
84131
let program_account = &instruction_accounts[0];
85132
let instruction_accounts = &instruction_accounts[1..];
86133

87-
execute_cpi(instruction_accounts, cpi_data, program_account, &wallet_signer)?;
134+
if let Err(e) = execute_cpi(instruction_accounts, cpi_data, program_account, &wallet_signer) {
135+
msg!("InvalidInstruction: Failed to execute CPI instruction {}: {:?}", idx, e);
136+
return Ok(());
137+
}
88138
}
139+
140+
let last_nonce = ctx.accounts.wallet_state.last_nonce;
141+
ctx.accounts.wallet_state.last_nonce = validation::safe_increment_nonce(last_nonce);
89142

90143
Ok(())
91144
}
@@ -103,6 +156,7 @@ pub struct ExecuteChunk<'info> {
103156
pub smart_wallet: SystemAccount<'info>,
104157

105158
#[account(
159+
mut,
106160
seeds = [WalletState::PREFIX_SEED, smart_wallet.key().as_ref()],
107161
bump,
108162
owner = ID,
Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
mod create_chunk;
22
mod execute_chunk;
3-
mod close_chunk;
43

54
pub use create_chunk::*;
65
pub use execute_chunk::*;
7-
pub use close_chunk::*;

‎programs/lazorkit/src/lib.rs‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,4 @@ pub mod lazorkit {
4444
) -> Result<()> {
4545
instructions::execute_chunk(ctx, instruction_data_list, split_index)
4646
}
47-
48-
pub fn close_chunk(ctx: Context<CloseChunk>) -> Result<()> {
49-
instructions::close_chunk(ctx)
50-
}
5147
}

0 commit comments

Comments
 (0)