1use crate::address::Address;
4use crate::error::ProgramError;
5use crate::AccountView;
6
7pub const MAX_SEED_LEN: usize = 32;
9
10const PDA_MARKER: &[u8; 21] = b"ProgramDerivedAddress";
14
15#[inline]
23pub fn create_with_seed(
24 base: &Address,
25 seed: &[u8],
26 owner: &Address,
27) -> Result<Address, ProgramError> {
28 if seed.len() > MAX_SEED_LEN {
29 return Err(ProgramError::MaxSeedLengthExceeded);
30 }
31 let owner_bytes = owner.as_array();
32 if owner_bytes[32 - PDA_MARKER.len()..] == PDA_MARKER[..] {
33 return Err(ProgramError::IllegalOwner);
34 }
35 let digest = hopper_native::hash::sha256(&[base.as_array(), seed, owner_bytes])
36 .map_err(|_| ProgramError::InvalidArgument)?;
37 Ok(Address::new_from_array(digest))
38}
39
40#[inline]
43pub fn verify_address_with_seed(
44 expected: &Address,
45 base: &Address,
46 seed: &[u8],
47 owner: &Address,
48) -> Result<(), ProgramError> {
49 if create_with_seed(base, seed, owner)? == *expected {
50 Ok(())
51 } else {
52 Err(ProgramError::InvalidSeeds)
53 }
54}
55
56#[inline]
61pub fn create_program_address(
62 seeds: &[&[u8]],
63 program_id: &Address,
64) -> Result<Address, ProgramError> {
65 crate::native_boundary::create_program_address(seeds, program_id)
66}
67
68#[inline]
81pub fn find_program_address(seeds: &[&[u8]], program_id: &Address) -> (Address, u8) {
82 crate::native_boundary::find_program_address(seeds, program_id)
83}
84
85#[inline]
92pub fn try_find_program_address(
93 seeds: &[&[u8]],
94 program_id: &Address,
95) -> Result<(Address, u8), ProgramError> {
96 let backend = hopper_native::Address::new_from_array(*program_id.as_array());
97 hopper_native::pda::based_try_find_program_address(seeds, &backend)
98 .map(|(address, bump)| (Address::new_from_array(address.to_bytes()), bump))
99 .map_err(ProgramError::from)
100}
101
102pub const fn find_program_address_const(seeds: &[&[u8]], program_id: &Address) -> (Address, u8) {
106 let backend = hopper_native::address::Address::new_from_array(*program_id.as_array());
107 let (address, bump) = hopper_native::pda::find_program_address_const(seeds, &backend);
108 (Address::new_from_array(address.to_bytes()), bump)
109}
110
111#[inline(always)]
113pub fn derive(seeds: &[&[u8]], program_id: &Address) -> (Address, u8) {
114 find_program_address(seeds, program_id)
115}
116
117pub const fn const_program_address(program_id: &Address, seeds: &[&[u8]], bump: u8) -> Address {
135 let backend = hopper_native::address::Address::new_from_array(*program_id.as_array());
136 Address::new_from_array(
137 hopper_native::pda::program_address_const(seeds, bump, &backend).to_bytes(),
138 )
139}
140
141#[inline]
153pub fn verify_pda_address(
154 seeds: &[&[u8]],
155 program_id: &Address,
156 expected: &Address,
157) -> Result<(), ProgramError> {
158 hopper_native::pda::verify_program_address(
159 seeds,
160 crate::native_boundary::as_backend_address(program_id),
161 crate::native_boundary::as_backend_address(expected),
162 )
163 .map_err(ProgramError::from)
164}
165
166#[cold]
174#[inline(never)]
175pub fn verify_pda_address_cold(
176 seeds: &[&[u8]],
177 program_id: &Address,
178 expected: &Address,
179) -> Result<(), ProgramError> {
180 verify_pda_address(seeds, program_id, expected)
181}
182
183#[inline]
186pub fn verify_pda_address_checked(
187 seeds: &[&[u8]],
188 program_id: &Address,
189 expected: &Address,
190) -> Result<(), ProgramError> {
191 let derived = create_program_address(seeds, program_id)?;
192 if crate::address::address_eq(&derived, expected) {
193 Ok(())
194 } else {
195 Err(ProgramError::InvalidSeeds)
196 }
197}
198
199#[inline]
210pub fn find_bump_for_address(
211 seeds: &[&[u8]],
212 program_id: &Address,
213 expected: &Address,
214) -> Result<u8, ProgramError> {
215 hopper_native::pda::find_bump_for_address(
216 seeds,
217 crate::native_boundary::as_backend_address(program_id),
218 crate::native_boundary::as_backend_address(expected),
219 )
220 .map_err(ProgramError::from)
221}
222
223#[inline]
226pub fn find_canonical_bump_checked(
227 seeds: &[&[u8]],
228 program_id: &Address,
229 expected: &Address,
230) -> Result<u8, ProgramError> {
231 #[cfg(target_os = "solana")]
232 let (derived, bump) = hopper_native::pda::based_try_find_program_address(
233 seeds,
234 crate::native_boundary::as_backend_address(program_id),
235 )
236 .map(|(address, bump)| (Address::new_from_array(address.to_bytes()), bump))
237 .map_err(ProgramError::from)?;
238 #[cfg(not(target_os = "solana"))]
239 let (derived, bump) = find_program_address(seeds, program_id);
240 if crate::address::address_eq(&derived, expected) {
241 Ok(bump)
242 } else {
243 Err(ProgramError::InvalidSeeds)
244 }
245}
246
247#[inline]
249pub fn verify_pda(
250 account: &AccountView<'_>,
251 seeds: &[&[u8]],
252 program_id: &Address,
253) -> Result<(), ProgramError> {
254 #[cfg(target_os = "solana")]
255 {
256 hopper_native::pda::verify_pda(
257 account.as_backend(),
258 seeds,
259 crate::native_boundary::as_backend_address(program_id),
260 )
261 .map_err(ProgramError::from)
262 }
263
264 #[cfg(not(target_os = "solana"))]
265 {
266 let expected = create_program_address(seeds, program_id)?;
267 if crate::address::address_eq(account.address(), &expected) {
268 Ok(())
269 } else {
270 Err(ProgramError::InvalidSeeds)
271 }
272 }
273}
274
275#[inline]
277pub fn verify_pda_with_bump(
278 account: &AccountView<'_>,
279 seeds: &[&[u8]],
280 bump: u8,
281 program_id: &Address,
282) -> Result<(), ProgramError> {
283 #[cfg(target_os = "solana")]
284 {
285 hopper_native::pda::verify_pda_with_bump(
286 account.as_backend(),
287 seeds,
288 bump,
289 crate::native_boundary::as_backend_address(program_id),
290 )
291 .map_err(ProgramError::from)
292 }
293
294 #[cfg(not(target_os = "solana"))]
295 {
296 if seeds.len() >= 16 {
297 return Err(ProgramError::InvalidSeeds);
298 }
299 let mut full_seeds: [&[u8]; 16] = [&[]; 16];
300 let num = seeds.len();
301 let mut i = 0;
302 while i < num {
303 full_seeds[i] = seeds[i];
304 i += 1;
305 }
306 let bump_bytes = [bump];
307 full_seeds[num] = &bump_bytes;
308
309 let expected = create_program_address(&full_seeds[..num + 1], program_id)?;
310 if crate::address::address_eq(account.address(), &expected) {
311 Ok(())
312 } else {
313 Err(ProgramError::InvalidSeeds)
314 }
315 }
316}
317
318#[inline]
330pub fn find_and_verify_pda(
331 account: &AccountView<'_>,
332 seeds: &[&[u8]],
333 program_id: &Address,
334) -> Result<u8, ProgramError> {
335 #[cfg(target_os = "solana")]
336 {
337 let expected_addr = account.as_backend().address();
338 let backend_expected =
339 unsafe { &*(expected_addr as *const hopper_native::address::Address) };
342 verify_pda_sha256_loop(backend_expected, seeds, program_id)
343 }
344
345 #[cfg(not(target_os = "solana"))]
346 {
347 let (expected, bump) = find_program_address(seeds, program_id);
348 if crate::address::address_eq(account.address(), &expected) {
349 Ok(bump)
350 } else {
351 Err(ProgramError::InvalidSeeds)
352 }
353 }
354}
355
356#[inline]
360pub fn verify_pda_strict(
361 expected: &Address,
362 seeds: &[&[u8]],
363 program_id: &Address,
364) -> Result<(), ProgramError> {
365 #[cfg(target_os = "solana")]
366 {
367 let backend_expected =
368 unsafe { &*(expected as *const Address as *const hopper_native::address::Address) };
371 verify_pda_sha256_loop(backend_expected, seeds, program_id).map(|_| ())
372 }
373
374 #[cfg(not(target_os = "solana"))]
375 {
376 let (derived, _) = find_program_address(seeds, program_id);
377 if crate::address::address_eq(&derived, expected) {
378 Ok(())
379 } else {
380 Err(ProgramError::InvalidSeeds)
381 }
382 }
383}
384
385#[cfg(target_os = "solana")]
400#[inline(always)]
401fn verify_pda_sha256_loop(
402 expected: &hopper_native::address::Address,
403 seeds: &[&[u8]],
404 program_id: &Address,
405) -> Result<u8, ProgramError> {
406 hopper_native::pda::find_bump_for_address(
409 seeds,
410 crate::native_boundary::as_backend_address(program_id),
411 expected,
412 )
413 .map_err(ProgramError::from)
414}
415
416#[inline]
421pub fn verify_pda_from_stored_bump(
422 account: &AccountView<'_>,
423 seeds: &[&[u8]],
424 bump_offset: usize,
425 program_id: &Address,
426) -> Result<(), ProgramError> {
427 #[cfg(target_os = "solana")]
428 {
429 hopper_native::verify_pda_from_stored_bump(
430 account.as_backend(),
431 seeds,
432 bump_offset,
433 crate::native_boundary::as_backend_address(program_id),
434 )
435 .map_err(ProgramError::from)
436 }
437
438 #[cfg(not(target_os = "solana"))]
439 {
440 let data = account.try_borrow()?;
442 if bump_offset >= data.len() {
443 return Err(ProgramError::AccountDataTooSmall);
444 }
445 let bump = data[bump_offset];
446 if seeds.len() >= 16 {
447 return Err(ProgramError::InvalidSeeds);
448 }
449 let mut full_seeds: [&[u8]; 16] = [&[]; 16];
450 let num = seeds.len();
451 let mut i = 0;
452 while i < num {
453 full_seeds[i] = seeds[i];
454 i += 1;
455 }
456 let bump_bytes = [bump];
457 full_seeds[num] = &bump_bytes;
458
459 let expected = create_program_address(&full_seeds[..num + 1], program_id)?;
460 if crate::address::address_eq(account.address(), &expected) {
461 Ok(())
462 } else {
463 Err(ProgramError::InvalidSeeds)
464 }
465 }
466}
467
468#[cfg(test)]
469mod tests {
470 use super::*;
471
472 #[test]
477 fn const_program_address_matches_devnet_created_pdas() {
478 const PROGRAM: Address = crate::address!("F4Um7PWsnZfN7y8WFzu1aPYJwqGduJTa4zuCGY9EUqMy");
479 const PAYER: Address = crate::address!("4sbBUbY71JFeA4kJckBmNnTADiFu4jtu84Gzev52ZEhn");
480 const AUTHORITY_C: Address =
481 crate::address!("7Qj28pSptq3YEdppwTmxDEP4jLsS1o67D1ZfKQJB9SE2");
482 const PINA_COUNTER: Address =
483 crate::address!("GJQcuWrT2f3f4KNuJcXhhwUa1ZQTYbxzzJ1hotzKu8hS");
484
485 const PDA_A: Address = crate::const_pda!(PROGRAM, [b"counter", PAYER.as_array()], 252);
486 const PDA_C: Address =
487 crate::const_pda!(PROGRAM, [b"counter", AUTHORITY_C.as_array()], 254);
488 const PDA_PINA: Address =
489 const_program_address(&PINA_COUNTER, &[b"counter", PAYER.as_array()], 253);
490
491 assert_eq!(
492 PDA_A,
493 crate::address!("Cn3JBYNBEctRDGuotxM7c3Fz3QgCZgRXkKV1G7h1qZKn")
494 );
495 assert_eq!(
496 PDA_C,
497 crate::address!("6vh34eBGs3gvwdaJ3fgXDLQMtNfqUwSJYrHqZ3FgCwYP")
498 );
499 assert_eq!(
500 PDA_PINA,
501 crate::address!("CW1z5aL4hTAFFubWKVKw1ANkdYurNEAiWbqxKsDCaERH")
502 );
503 assert_ne!(
505 const_program_address(&PROGRAM, &[b"counter", PAYER.as_array()], 251),
506 PDA_A
507 );
508 }
509}
510
511#[cfg(test)]
512mod seeded_address_tests {
513 use super::*;
514 use solana_pubkey::Pubkey;
515
516 #[test]
517 fn fallible_search_matches_sdk_and_rejects_invalid_seed_shapes() {
518 let program = Address::new_from_array([9; 32]);
519 for seed in [b"".as_slice(), b"vault", &[7; 32]] {
520 let (address, bump) = try_find_program_address(&[seed], &program).unwrap();
521 let (expected, expected_bump) =
522 Pubkey::find_program_address(&[seed], &Pubkey::new_from_array([9; 32]));
523 assert_eq!(address.to_bytes(), expected.to_bytes());
524 assert_eq!(bump, expected_bump);
525 }
526 assert_eq!(
527 try_find_program_address(&[&[0; 33]], &program),
528 Err(ProgramError::InvalidSeeds)
529 );
530 assert_eq!(
531 try_find_program_address(&[b"x".as_slice(); 16], &program),
532 Err(ProgramError::InvalidSeeds)
533 );
534 }
535
536 #[test]
537 fn create_with_seed_matches_the_canonical_derivation() {
538 let base = Address::new_from_array([3; 32]);
539 let owner = Address::new_from_array([9; 32]);
540 for seed in ["", "vault", "0123456789abcdef0123456789abcdef", "caf\u{e9}"] {
541 let canonical = Pubkey::create_with_seed(
542 &Pubkey::new_from_array([3; 32]),
543 seed,
544 &Pubkey::new_from_array([9; 32]),
545 )
546 .unwrap();
547 let derived = create_with_seed(&base, seed.as_bytes(), &owner).unwrap();
548 assert_eq!(derived.as_array(), &canonical.to_bytes(), "seed {seed:?}");
549 assert_eq!(
550 verify_address_with_seed(&derived, &base, seed.as_bytes(), &owner),
551 Ok(())
552 );
553 assert_eq!(
554 verify_address_with_seed(&base, &base, seed.as_bytes(), &owner),
555 Err(ProgramError::InvalidSeeds)
556 );
557 }
558 }
559
560 #[test]
561 fn create_with_seed_refuses_what_the_runtime_refuses() {
562 let base = Address::new_from_array([3; 32]);
563 let owner = Address::new_from_array([9; 32]);
564 assert_eq!(
565 create_with_seed(&base, &[b'x'; 33], &owner),
566 Err(ProgramError::MaxSeedLengthExceeded)
567 );
568 let mut marked = [9u8; 32];
569 marked[11..].copy_from_slice(b"ProgramDerivedAddress");
570 assert_eq!(
571 create_with_seed(&base, b"vault", &Address::new_from_array(marked)),
572 Err(ProgramError::IllegalOwner)
573 );
574 assert!(Pubkey::create_with_seed(
575 &Pubkey::new_from_array([3; 32]),
576 "vault",
577 &Pubkey::new_from_array(marked),
578 )
579 .is_err());
580 }
581}