Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.

Commit c6a7f49

Browse files
authored
Allow withdrawer to change the authorized stake key (#8456)
1 parent d821fd2 commit c6a7f49

File tree

1 file changed

+88
-3
lines changed

1 file changed

+88
-3
lines changed

programs/stake/src/stake_state.rs

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -340,10 +340,18 @@ impl Authorized {
340340
new_authorized: &Pubkey,
341341
stake_authorize: StakeAuthorize,
342342
) -> Result<(), InstructionError> {
343-
self.check(signers, stake_authorize)?;
344343
match stake_authorize {
345-
StakeAuthorize::Staker => self.staker = *new_authorized,
346-
StakeAuthorize::Withdrawer => self.withdrawer = *new_authorized,
344+
StakeAuthorize::Staker => {
345+
// Allow either the staker or the withdrawer to change the staker key
346+
if !signers.contains(&self.staker) && !signers.contains(&self.withdrawer) {
347+
return Err(InstructionError::MissingRequiredSignature);
348+
}
349+
self.staker = *new_authorized
350+
}
351+
StakeAuthorize::Withdrawer => {
352+
self.check(signers, stake_authorize)?;
353+
self.withdrawer = *new_authorized
354+
}
347355
}
348356
Ok(())
349357
}
@@ -2274,6 +2282,83 @@ mod tests {
22742282
);
22752283
}
22762284

2285+
#[test]
2286+
fn test_authorize_override() {
2287+
let withdrawer_pubkey = Pubkey::new_rand();
2288+
let stake_lamports = 42;
2289+
let stake_account = Account::new_ref_data_with_space(
2290+
stake_lamports,
2291+
&StakeState::Initialized(Meta::auto(&withdrawer_pubkey)),
2292+
std::mem::size_of::<StakeState>(),
2293+
&id(),
2294+
)
2295+
.expect("stake_account");
2296+
2297+
let stake_keyed_account = KeyedAccount::new(&withdrawer_pubkey, true, &stake_account);
2298+
2299+
// Authorize a staker pubkey and move the withdrawer key into cold storage.
2300+
let stake_pubkey = Pubkey::new_rand();
2301+
let signers = vec![withdrawer_pubkey].into_iter().collect();
2302+
assert_eq!(
2303+
stake_keyed_account.authorize(
2304+
&stake_pubkey,
2305+
StakeAuthorize::Staker,
2306+
&signers,
2307+
&Clock::default()
2308+
),
2309+
Ok(())
2310+
);
2311+
2312+
// Attack! The stake key (a hot key) is stolen and used to authorize a new staker.
2313+
let mallory_pubkey = Pubkey::new_rand();
2314+
let signers = vec![stake_pubkey].into_iter().collect();
2315+
assert_eq!(
2316+
stake_keyed_account.authorize(
2317+
&mallory_pubkey,
2318+
StakeAuthorize::Staker,
2319+
&signers,
2320+
&Clock::default()
2321+
),
2322+
Ok(())
2323+
);
2324+
2325+
// Verify the original staker no longer has access.
2326+
let new_stake_pubkey = Pubkey::new_rand();
2327+
assert_eq!(
2328+
stake_keyed_account.authorize(
2329+
&new_stake_pubkey,
2330+
StakeAuthorize::Staker,
2331+
&signers,
2332+
&Clock::default()
2333+
),
2334+
Err(InstructionError::MissingRequiredSignature)
2335+
);
2336+
2337+
// Verify the withdrawer (pulled from cold storage) can save the day.
2338+
let signers = vec![withdrawer_pubkey].into_iter().collect();
2339+
assert_eq!(
2340+
stake_keyed_account.authorize(
2341+
&new_stake_pubkey,
2342+
StakeAuthorize::Withdrawer,
2343+
&signers,
2344+
&Clock::default()
2345+
),
2346+
Ok(())
2347+
);
2348+
2349+
// Attack! Verify the staker cannot be used to authorize a withdraw.
2350+
let signers = vec![new_stake_pubkey].into_iter().collect();
2351+
assert_eq!(
2352+
stake_keyed_account.authorize(
2353+
&mallory_pubkey,
2354+
StakeAuthorize::Withdrawer,
2355+
&signers,
2356+
&Clock::default()
2357+
),
2358+
Ok(())
2359+
);
2360+
}
2361+
22772362
#[test]
22782363
fn test_split_source_uninitialized() {
22792364
let stake_pubkey = Pubkey::new_rand();

0 commit comments

Comments
 (0)