use crate::account_view::AccountView;
use crate::address::{Address, MAX_SEEDS, MAX_SEED_LEN};
use crate::error::ProgramError;
#[cfg(target_os = "solana")]
const CURVE25519_EDWARDS: u64 = 0;
#[cfg(target_os = "solana")]
const PDA_MARKER_BYTES: &[u8; 21] = crate::address::PDA_MARKER;
#[inline(always)]
fn validate_seeds(seeds: &[&[u8]], max_count: usize) -> Result<(), ProgramError> {
if seeds.len() > max_count || seeds.iter().any(|seed| seed.len() > MAX_SEED_LEN) {
return Err(ProgramError::InvalidSeeds);
}
Ok(())
}
#[inline(always)]
pub fn create_program_address(
seeds: &[&[u8]],
program_id: &Address,
) -> Result<Address, ProgramError> {
validate_seeds(seeds, MAX_SEEDS)?;
#[cfg(target_os = "solana")]
{
const _: () = assert!(core::mem::size_of::<&[u8]>() == 16);
let mut result = Address::default();
let rc = unsafe {
crate::syscalls::sol_create_program_address(
seeds.as_ptr() as *const u8,
seeds.len() as u64,
program_id.as_array().as_ptr(),
result.0.as_mut_ptr(),
)
};
if rc == 0 {
Ok(result)
} else {
Err(ProgramError::InvalidSeeds)
}
}
#[cfg(not(target_os = "solana"))]
{
let _ = (seeds, program_id);
Err(ProgramError::InvalidSeeds)
}
}
#[inline(always)]
pub fn find_program_address(seeds: &[&[u8]], program_id: &Address) -> (Address, u8) {
#[cfg(target_os = "solana")]
{
match based_try_find_program_address(seeds, program_id) {
Ok(found) => found,
Err(_) => panic!("hopper: unable to find a viable program address bump seed"),
}
}
#[cfg(not(target_os = "solana"))]
{
let _ = (seeds, program_id);
panic!(
"hopper: find_program_address requires the SVM sha256 syscall (target_os = \"solana\")"
);
}
}
pub const fn program_address_const(seeds: &[&[u8]], bump: u8, program_id: &Address) -> Address {
assert!(
seeds.len() < MAX_SEEDS,
"a PDA takes at most 15 seeds plus its bump"
);
let mut hasher = crate::sha256::ConstSha256::new();
let mut i = 0;
while i < seeds.len() {
assert!(
seeds[i].len() <= crate::address::MAX_SEED_LEN,
"a PDA seed is at most 32 bytes"
);
hasher = hasher.update(seeds[i]);
i += 1;
}
let hash = hasher
.update(&[bump])
.update(program_id.as_array())
.update(crate::address::PDA_MARKER)
.finalize();
Address::new_from_array(hash)
}
#[inline(always)]
pub fn verify_program_address(
seeds: &[&[u8]],
program_id: &Address,
expected: &Address,
) -> Result<(), ProgramError> {
validate_seeds(seeds, MAX_SEEDS)?;
#[cfg(target_os = "solana")]
{
let n = seeds.len();
let mut slices = core::mem::MaybeUninit::<[&[u8]; MAX_SEEDS + 2]>::uninit();
let slice_ptr = slices.as_mut_ptr() as *mut &[u8];
let mut i = 0;
while i < n {
unsafe { slice_ptr.add(i).write(seeds[i]) };
i += 1;
}
unsafe {
slice_ptr.add(n).write(program_id.as_ref());
slice_ptr.add(n + 1).write(PDA_MARKER_BYTES.as_slice());
}
let input = unsafe { core::slice::from_raw_parts(slice_ptr, n + 2) };
let mut hash = core::mem::MaybeUninit::<[u8; 32]>::uninit();
unsafe {
crate::syscalls::sol_sha256(
input as *const _ as *const u8,
input.len() as u64,
hash.as_mut_ptr() as *mut u8,
);
}
let derived = unsafe { &*(hash.as_ptr() as *const Address) };
if derived == expected {
Ok(())
} else {
Err(ProgramError::InvalidSeeds)
}
}
#[cfg(not(target_os = "solana"))]
{
let _ = (seeds, program_id, expected);
Err(ProgramError::InvalidSeeds)
}
}
#[inline(always)]
pub fn based_try_find_program_address(
seeds: &[&[u8]],
program_id: &Address,
) -> Result<(Address, u8), ProgramError> {
validate_seeds(seeds, MAX_SEEDS - 1)?;
#[cfg(target_os = "solana")]
{
let n = seeds.len();
let mut slices = core::mem::MaybeUninit::<[&[u8]; MAX_SEEDS + 2]>::uninit();
let slice_ptr = slices.as_mut_ptr() as *mut &[u8];
let mut i = 0;
while i < n {
unsafe { slice_ptr.add(i).write(seeds[i]) };
i += 1;
}
unsafe {
slice_ptr.add(n + 1).write(program_id.as_ref());
slice_ptr.add(n + 2).write(PDA_MARKER_BYTES.as_slice());
}
let mut hash = core::mem::MaybeUninit::<[u8; 32]>::uninit();
let mut bump: u64 = u8::MAX as u64;
loop {
let bump_seed = [bump as u8];
unsafe {
slice_ptr
.add(n)
.write(core::slice::from_raw_parts(bump_seed.as_ptr(), 1))
};
let input = unsafe { core::slice::from_raw_parts(slice_ptr, n + 3) };
unsafe {
crate::syscalls::sol_sha256(
input as *const _ as *const u8,
input.len() as u64,
hash.as_mut_ptr() as *mut u8,
);
}
let curve_rc = unsafe {
crate::syscalls::sol_curve_validate_point(
CURVE25519_EDWARDS,
hash.as_ptr() as *const u8,
core::ptr::null_mut(),
)
};
if curve_rc != 0 {
return Ok((
Address::new_from_array(unsafe { hash.assume_init() }),
bump as u8,
));
}
if bump == 0 {
break;
}
bump -= 1;
}
Err(ProgramError::InvalidSeeds)
}
#[cfg(not(target_os = "solana"))]
{
let _ = (seeds, program_id);
Err(ProgramError::InvalidSeeds)
}
}
#[inline(always)]
pub fn verify_pda(
account: &AccountView<'_>,
seeds: &[&[u8]],
program_id: &Address,
) -> Result<(), ProgramError> {
let expected = create_program_address(seeds, program_id)?;
if account.address() == &expected {
Ok(())
} else {
Err(ProgramError::InvalidSeeds)
}
}
#[inline]
pub fn verify_pda_with_bump(
account: &AccountView<'_>,
seeds: &[&[u8]],
bump: u8,
program_id: &Address,
) -> Result<(), ProgramError> {
validate_seeds(seeds, MAX_SEEDS - 1)?;
let mut full_seeds: [&[u8]; MAX_SEEDS] = [&[]; MAX_SEEDS];
let num = seeds.len();
let mut i = 0;
while i < num {
full_seeds[i] = seeds[i];
i += 1;
}
let bump_bytes = [bump];
full_seeds[num] = &bump_bytes;
verify_program_address(&full_seeds[..num + 1], program_id, account.address())
}
#[inline]
pub fn verify_pda_strict(
expected: &Address,
seeds: &[&[u8]],
program_id: &Address,
) -> Result<(), ProgramError> {
verify_program_address(seeds, program_id, expected)
}
#[inline(always)]
pub fn find_bump_for_address(
seeds: &[&[u8]],
program_id: &Address,
expected: &Address,
) -> Result<u8, ProgramError> {
validate_seeds(seeds, MAX_SEEDS - 1)?;
#[cfg(target_os = "solana")]
{
let n = seeds.len();
let mut slices = core::mem::MaybeUninit::<[&[u8]; MAX_SEEDS + 2]>::uninit();
let slice_ptr = slices.as_mut_ptr() as *mut &[u8];
let mut i = 0;
while i < n {
unsafe { slice_ptr.add(i).write(seeds[i]) };
i += 1;
}
unsafe {
slice_ptr.add(n + 1).write(program_id.as_ref());
slice_ptr.add(n + 2).write(PDA_MARKER_BYTES.as_slice());
}
let mut hash = core::mem::MaybeUninit::<[u8; 32]>::uninit();
let mut bump: u64 = u8::MAX as u64;
loop {
let bump_seed = [bump as u8];
unsafe {
slice_ptr
.add(n)
.write(core::slice::from_raw_parts(bump_seed.as_ptr(), 1))
};
let input = unsafe { core::slice::from_raw_parts(slice_ptr, n + 3) };
unsafe {
crate::syscalls::sol_sha256(
input as *const _ as *const u8,
input.len() as u64,
hash.as_mut_ptr() as *mut u8,
);
}
let derived = unsafe { &*(hash.as_ptr() as *const Address) };
if derived == expected {
return Ok(bump as u8);
}
if bump == 0 {
break;
}
bump -= 1;
}
Err(ProgramError::InvalidSeeds)
}
#[cfg(not(target_os = "solana"))]
{
let _ = (seeds, program_id, expected);
Err(ProgramError::InvalidSeeds)
}
}
#[inline(always)]
pub fn read_bump_from_account(
account: &AccountView<'_>,
bump_offset: usize,
) -> Result<u8, ProgramError> {
let data = account.try_borrow()?;
if data.len() <= bump_offset {
return Err(ProgramError::AccountDataTooSmall);
}
Ok(data[bump_offset])
}
#[inline]
pub fn verify_pda_from_stored_bump(
account: &AccountView<'_>,
seeds: &[&[u8]],
bump_offset: usize,
program_id: &Address,
) -> Result<(), ProgramError> {
validate_seeds(seeds, MAX_SEEDS - 1)?;
let bump = read_bump_from_account(account, bump_offset)?;
let mut full_seeds: [&[u8]; MAX_SEEDS] = [&[]; MAX_SEEDS];
let num = seeds.len();
let mut i = 0;
while i < num {
full_seeds[i] = seeds[i];
i += 1;
}
let bump_bytes = [bump];
full_seeds[num] = &bump_bytes;
verify_program_address(&full_seeds[..num + 1], program_id, account.address())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn const_pda_accepts_fifteen_base_seeds() {
let id = Address::new_from_array([91; 32]);
let fifteen: [&[u8]; 15] = [&[]; 15];
assert_eq!(
program_address_const(&fifteen, 255, &id),
program_address_const(&[], 255, &id)
);
}
#[test]
#[should_panic(expected = "at most 15 seeds plus its bump")]
fn const_pda_reserves_the_bump_slot() {
let seeds: [&[u8]; 16] = [&[]; 16];
program_address_const(&seeds, 255, &Address::new_from_array([91; 32]));
}
#[test]
#[should_panic(expected = "at most 32 bytes")]
fn const_pda_rejects_oversized_seeds() {
program_address_const(&[&[0; 33]], 255, &Address::new_from_array([91; 32]));
}
}