solana_address/syscalls.rs
1#[cfg(all(
2 not(any(target_os = "solana", target_arch = "bpf")),
3 feature = "curve25519"
4))]
5use crate::bytes_are_curve_point;
6#[cfg(any(target_os = "solana", target_arch = "bpf", feature = "curve25519"))]
7use crate::error::AddressError;
8use crate::Address;
9/// Syscall definitions used by `solana_address`.
10#[cfg(any(target_os = "solana", target_arch = "bpf"))]
11pub use solana_define_syscall::definitions::{
12 sol_create_program_address, sol_log_pubkey, sol_try_find_program_address,
13};
14
15/// Copied from `solana_program::entrypoint::SUCCESS`
16/// to avoid a `solana_program` dependency
17#[cfg(any(target_os = "solana", target_arch = "bpf"))]
18const SUCCESS: u64 = 0;
19
20impl Address {
21 /// Log an `Address` value.
22 #[cfg(any(target_os = "solana", target_arch = "bpf"))]
23 pub fn log(&self) {
24 unsafe { sol_log_pubkey(self.as_ref() as *const _ as *const u8) };
25 }
26
27 /// Find a valid [program derived address][pda] and its corresponding bump seed.
28 ///
29 /// [pda]: https://solana.com/docs/core/cpi#program-derived-addresses
30 ///
31 /// Program derived addresses (PDAs) are account keys that only the program,
32 /// `program_id`, has the authority to sign. The address is of the same form
33 /// as a Solana `Address`, except they are ensured to not be on the ed25519
34 /// curve and thus have no associated private key. When performing
35 /// cross-program invocations the program can "sign" for the key by calling
36 /// [`invoke_signed`] and passing the same seeds used to generate the
37 /// address, along with the calculated _bump seed_, which this function
38 /// returns as the second tuple element. The runtime will verify that the
39 /// program associated with this address is the caller and thus authorized
40 /// to be the signer.
41 ///
42 /// [`invoke_signed`]: https://docs.rs/solana-program/latest/solana_program/program/fn.invoke_signed.html
43 ///
44 /// The `seeds` are application-specific, and must be carefully selected to
45 /// uniquely derive accounts per application requirements. It is common to
46 /// use static strings and other addresses as seeds.
47 ///
48 /// Because the program address must not lie on the ed25519 curve, there may
49 /// be seed and program id combinations that are invalid. For this reason,
50 /// an extra seed (the bump seed) is calculated that results in a
51 /// point off the curve. The bump seed must be passed as an additional seed
52 /// when calling `invoke_signed`.
53 ///
54 /// The processes of finding a valid program address is by trial and error,
55 /// and even though it is deterministic given a set of inputs it can take a
56 /// variable amount of time to succeed across different inputs. This means
57 /// that when called from an on-chain program it may incur a variable amount
58 /// of the program's compute budget. Programs that are meant to be very
59 /// performant may not want to use this function because it could take a
60 /// considerable amount of time. Programs that are already at risk
61 /// of exceeding their compute budget should call this with care since
62 /// there is a chance that the program's budget may be occasionally
63 /// and unpredictably exceeded.
64 ///
65 /// As all account addresses accessed by an on-chain Solana program must be
66 /// explicitly passed to the program, it is typical for the PDAs to be
67 /// derived in off-chain client programs, avoiding the compute cost of
68 /// generating the address on-chain. The address may or may not then be
69 /// verified by re-deriving it on-chain, depending on the requirements of
70 /// the program. This verification may be performed without the overhead of
71 /// re-searching for the bump key by using the [`create_program_address`]
72 /// function.
73 ///
74 /// [`create_program_address`]: Address::create_program_address
75 ///
76 /// **Warning**: Because of the way the seeds are hashed there is a potential
77 /// for program address collisions for the same program id. The seeds are
78 /// hashed sequentially which means that seeds {"abcdef"}, {"abc", "def"},
79 /// and {"ab", "cd", "ef"} will all result in the same program address given
80 /// the same program id. Since the chance of collision is local to a given
81 /// program id, the developer of that program must take care to choose seeds
82 /// that do not collide with each other. For seed schemes that are susceptible
83 /// to this type of hash collision, a common remedy is to insert separators
84 /// between seeds, e.g. transforming {"abc", "def"} into {"abc", "-", "def"}.
85 ///
86 /// # Panics
87 ///
88 /// Panics in the statistically improbable event that a bump seed could not be
89 /// found. Use [`try_find_program_address`] to handle this case.
90 ///
91 /// [`try_find_program_address`]: Address::try_find_program_address
92 ///
93 /// Panics if any of the following are true:
94 ///
95 /// - the number of provided seeds is greater than, _or equal to_, [`crate::MAX_SEEDS`],
96 /// - any individual seed's length is greater than [`crate::MAX_SEED_LEN`].
97 ///
98 /// # Examples
99 ///
100 /// This example illustrates a simple case of creating a "vault" account
101 /// which is derived from the payer account, but owned by an on-chain
102 /// program. The program derived address is derived in an off-chain client
103 /// program, which invokes an on-chain Solana program that uses the address
104 /// to create a new account owned and controlled by the program itself.
105 ///
106 /// By convention, the on-chain program will be compiled for use in two
107 /// different contexts: both on-chain, to interpret a custom program
108 /// instruction as a Solana transaction; and off-chain, as a library, so
109 /// that clients can share the instruction data structure, constructors, and
110 /// other common code.
111 ///
112 /// First the on-chain Solana program:
113 ///
114 /// ```
115 /// # use borsh::{BorshSerialize, BorshDeserialize};
116 /// # use solana_account_info::{next_account_info, AccountInfo};
117 /// # use solana_program_error::ProgramResult;
118 /// # use solana_cpi::invoke_signed;
119 /// # use solana_address::Address;
120 /// # use solana_system_interface::instruction::create_account;
121 /// // The custom instruction processed by our program. It includes the
122 /// // PDA's bump seed, which is derived by the client program. This
123 /// // definition is also imported into the off-chain client program.
124 /// // The computed address of the PDA will be passed to this program via
125 /// // the `accounts` vector of the `Instruction` type.
126 /// #[derive(BorshSerialize, BorshDeserialize, Debug)]
127 /// # #[borsh(crate = "borsh")]
128 /// pub struct InstructionData {
129 /// pub vault_bump_seed: u8,
130 /// pub lamports: u64,
131 /// }
132 ///
133 /// // The size in bytes of a vault account. The client program needs
134 /// // this information to calculate the quantity of lamports necessary
135 /// // to pay for the account's rent.
136 /// pub static VAULT_ACCOUNT_SIZE: u64 = 1024;
137 ///
138 /// // The entrypoint of the on-chain program, as provided to the
139 /// // `entrypoint!` macro.
140 /// fn process_instruction(
141 /// program_id: &Address,
142 /// accounts: &[AccountInfo],
143 /// instruction_data: &[u8],
144 /// ) -> ProgramResult {
145 /// let account_info_iter = &mut accounts.iter();
146 /// let payer = next_account_info(account_info_iter)?;
147 /// // The vault PDA, derived from the payer's address
148 /// let vault = next_account_info(account_info_iter)?;
149 ///
150 /// let mut instruction_data = instruction_data;
151 /// let instr = InstructionData::deserialize(&mut instruction_data)?;
152 /// let vault_bump_seed = instr.vault_bump_seed;
153 /// let lamports = instr.lamports;
154 /// let vault_size = VAULT_ACCOUNT_SIZE;
155 ///
156 /// // Invoke the system program to create an account while virtually
157 /// // signing with the vault PDA, which is owned by this caller program.
158 /// invoke_signed(
159 /// &create_account(
160 /// &payer.key,
161 /// &vault.key,
162 /// lamports,
163 /// vault_size,
164 /// program_id,
165 /// ),
166 /// &[
167 /// payer.clone(),
168 /// vault.clone(),
169 /// ],
170 /// // A slice of seed slices, each seed slice being the set
171 /// // of seeds used to generate one of the PDAs required by the
172 /// // callee program, the final seed being a single-element slice
173 /// // containing the `u8` bump seed.
174 /// &[
175 /// &[
176 /// b"vault",
177 /// payer.key.as_ref(),
178 /// &[vault_bump_seed],
179 /// ],
180 /// ]
181 /// )?;
182 ///
183 /// Ok(())
184 /// }
185 /// ```
186 ///
187 /// The client program:
188 ///
189 /// ```
190 /// # use borsh::{BorshSerialize, BorshDeserialize};
191 /// # use solana_example_mocks::{solana_sdk, solana_rpc_client};
192 /// # use solana_address::Address;
193 /// # use solana_instruction::{AccountMeta, Instruction};
194 /// # use solana_hash::Hash;
195 /// # use solana_sdk::{
196 /// # signature::Keypair,
197 /// # signature::{Signer, Signature},
198 /// # transaction::Transaction,
199 /// # };
200 /// # use solana_rpc_client::rpc_client::RpcClient;
201 /// # use std::convert::TryFrom;
202 /// # use anyhow::Result;
203 /// #
204 /// # #[derive(BorshSerialize, BorshDeserialize, Debug)]
205 /// # #[borsh(crate = "borsh")]
206 /// # struct InstructionData {
207 /// # pub vault_bump_seed: u8,
208 /// # pub lamports: u64,
209 /// # }
210 /// #
211 /// # pub static VAULT_ACCOUNT_SIZE: u64 = 1024;
212 /// #
213 /// fn create_vault_account(
214 /// client: &RpcClient,
215 /// program_id: Address,
216 /// payer: &Keypair,
217 /// ) -> Result<()> {
218 /// // Derive the PDA from the payer account, a string representing the unique
219 /// // purpose of the account ("vault"), and the address of our on-chain program.
220 /// let (vault_address, vault_bump_seed) = Address::find_program_address(
221 /// &[b"vault", payer.pubkey().as_ref()],
222 /// &program_id
223 /// );
224 ///
225 /// // Get the amount of lamports needed to pay for the vault's rent
226 /// let vault_account_size = usize::try_from(VAULT_ACCOUNT_SIZE)?;
227 /// let lamports = client.get_minimum_balance_for_rent_exemption(vault_account_size)?;
228 ///
229 /// // The on-chain program's instruction data, imported from that program's crate.
230 /// let instr_data = InstructionData {
231 /// vault_bump_seed,
232 /// lamports,
233 /// };
234 ///
235 /// // The accounts required by both our on-chain program and the system program's
236 /// // `create_account` instruction, including the vault's address.
237 /// let accounts = vec![
238 /// AccountMeta::new(payer.pubkey(), true),
239 /// AccountMeta::new(vault_address, false),
240 /// AccountMeta::new(solana_system_interface::program::ID, false),
241 /// ];
242 ///
243 /// // Create the instruction by serializing our instruction data via borsh
244 /// let instruction = Instruction::new_with_borsh(
245 /// program_id,
246 /// &instr_data,
247 /// accounts,
248 /// );
249 ///
250 /// let blockhash = client.get_latest_blockhash()?;
251 ///
252 /// let transaction = Transaction::new_signed_with_payer(
253 /// &[instruction],
254 /// Some(&payer.pubkey()),
255 /// &[payer],
256 /// blockhash,
257 /// );
258 ///
259 /// client.send_and_confirm_transaction(&transaction)?;
260 ///
261 /// Ok(())
262 /// }
263 /// # let program_id = Address::new_unique();
264 /// # let payer = Keypair::new();
265 /// # let client = RpcClient::new(String::new());
266 /// #
267 /// # create_vault_account(&client, program_id, &payer)?;
268 /// #
269 /// # Ok::<(), anyhow::Error>(())
270 /// ```
271 // If target_os = "solana" or target_arch = "bpf", then the function
272 // will use syscalls which bring no dependencies; otherwise, this should
273 // be opt-in so users don't need the curve25519 dependency.
274 #[cfg(any(target_os = "solana", target_arch = "bpf", feature = "curve25519"))]
275 #[inline(always)]
276 pub fn find_program_address(seeds: &[&[u8]], program_id: &Address) -> (Address, u8) {
277 Self::try_find_program_address(seeds, program_id)
278 .unwrap_or_else(|| panic!("Unable to find a viable program address bump seed"))
279 }
280
281 /// Find a valid [program derived address][pda] and its corresponding bump seed.
282 ///
283 /// [pda]: https://solana.com/docs/core/cpi#program-derived-addresses
284 ///
285 /// The only difference between this method and [`find_program_address`]
286 /// is that this one returns `None` in the statistically improbable event
287 /// that a bump seed cannot be found; or if any of `find_program_address`'s
288 /// preconditions are violated.
289 ///
290 /// See the documentation for [`find_program_address`] for a full description.
291 ///
292 /// [`find_program_address`]: Address::find_program_address
293 // If target_os = "solana" or target_arch = "bpf", then the function
294 // will use syscalls which bring no dependencies; otherwise, this should
295 // be opt-in so users don't need the curve25519 dependency.
296 #[cfg(any(target_os = "solana", target_arch = "bpf", feature = "curve25519"))]
297 #[allow(clippy::same_item_push)]
298 #[inline(always)]
299 pub fn try_find_program_address(
300 seeds: &[&[u8]],
301 program_id: &Address,
302 ) -> Option<(Address, u8)> {
303 // Perform the calculation inline, calling this from within a program is
304 // not supported
305 #[cfg(not(any(target_os = "solana", target_arch = "bpf")))]
306 {
307 let mut bump_seed = [u8::MAX];
308 for _ in 0..u8::MAX {
309 {
310 let mut seeds_with_bump = seeds.to_vec();
311 seeds_with_bump.push(&bump_seed);
312 match Self::create_program_address(&seeds_with_bump, program_id) {
313 Ok(address) => return Some((address, bump_seed[0])),
314 Err(AddressError::InvalidSeeds) => (),
315 _ => break,
316 }
317 }
318 bump_seed[0] -= 1;
319 }
320 None
321 }
322 // Call via a system call to perform the calculation
323 #[cfg(any(target_os = "solana", target_arch = "bpf"))]
324 {
325 let mut bytes = core::mem::MaybeUninit::<Address>::uninit();
326 let mut bump_seed = u8::MAX;
327 let result = unsafe {
328 crate::syscalls::sol_try_find_program_address(
329 seeds as *const _ as *const u8,
330 seeds.len() as u64,
331 program_id as *const _ as *const u8,
332 &mut bytes as *mut _ as *mut u8,
333 &mut bump_seed as *mut _ as *mut u8,
334 )
335 };
336 match result {
337 // SAFETY: The syscall has initialized the bytes.
338 SUCCESS => Some((unsafe { bytes.assume_init() }, bump_seed)),
339 _ => None,
340 }
341 }
342 }
343
344 /// Create a valid [program derived address][pda] without searching for a bump seed.
345 ///
346 /// [pda]: https://solana.com/docs/core/cpi#program-derived-addresses
347 ///
348 /// Because this function does not create a bump seed, it may unpredictably
349 /// return an error for any given set of seeds and is not generally suitable
350 /// for creating program derived addresses.
351 ///
352 /// However, it can be used for efficiently verifying that a set of seeds plus
353 /// bump seed generated by [`find_program_address`] derives a particular
354 /// address as expected. See the example for details.
355 ///
356 /// See the documentation for [`find_program_address`] for a full description
357 /// of program derived addresses and bump seeds.
358 ///
359 /// [`find_program_address`]: Address::find_program_address
360 ///
361 /// # Examples
362 ///
363 /// Creating a program derived address involves iteratively searching for a
364 /// bump seed for which the derived [`Address`] does not lie on the ed25519
365 /// curve. This search process is generally performed off-chain, with the
366 /// [`find_program_address`] function, after which the client passes the
367 /// bump seed to the program as instruction data.
368 ///
369 /// Depending on the application requirements, a program may wish to verify
370 /// that the set of seeds, plus the bump seed, do correctly generate an
371 /// expected address.
372 ///
373 /// The verification is performed by appending to the other seeds one
374 /// additional seed slice that contains the single `u8` bump seed, calling
375 /// `create_program_address`, checking that the return value is `Ok`, and
376 /// that the returned `Address` has the expected value.
377 ///
378 /// ```
379 /// # use solana_address::Address;
380 /// # let program_id = Address::new_unique();
381 /// let (expected_pda, bump_seed) = Address::find_program_address(&[b"vault"], &program_id);
382 /// let actual_pda = Address::create_program_address(&[b"vault", &[bump_seed]], &program_id)?;
383 /// assert_eq!(expected_pda, actual_pda);
384 /// # Ok::<(), anyhow::Error>(())
385 /// ```
386 // If target_os = "solana" or target_arch = "bpf", then the function
387 // will use syscalls which bring no dependencies; otherwise, this should
388 // be opt-in so users don't need the curve25519 dependency.
389 #[cfg(any(target_os = "solana", target_arch = "bpf", feature = "curve25519"))]
390 #[inline(always)]
391 pub fn create_program_address(
392 seeds: &[&[u8]],
393 program_id: &Address,
394 ) -> Result<Address, AddressError> {
395 use crate::{MAX_SEEDS, MAX_SEED_LEN};
396
397 if seeds.len() > MAX_SEEDS {
398 return Err(AddressError::MaxSeedLengthExceeded);
399 }
400 if seeds.iter().any(|seed| seed.len() > MAX_SEED_LEN) {
401 return Err(AddressError::MaxSeedLengthExceeded);
402 }
403
404 // Perform the calculation inline, calling this from within a program is
405 // not supported
406 #[cfg(not(any(target_os = "solana", target_arch = "bpf")))]
407 {
408 use crate::PDA_MARKER;
409
410 let mut hasher = solana_sha256_hasher::Hasher::default();
411 for seed in seeds.iter() {
412 hasher.hash(seed);
413 }
414 hasher.hashv(&[program_id.as_ref(), PDA_MARKER]);
415 let hash = hasher.result();
416
417 if bytes_are_curve_point(hash.as_ref()) {
418 return Err(AddressError::InvalidSeeds);
419 }
420
421 Ok(Address::from(hash.to_bytes()))
422 }
423 // Call via a system call to perform the calculation
424 #[cfg(any(target_os = "solana", target_arch = "bpf"))]
425 {
426 let mut bytes = core::mem::MaybeUninit::<Address>::uninit();
427 let result = unsafe {
428 crate::syscalls::sol_create_program_address(
429 seeds as *const _ as *const u8,
430 seeds.len() as u64,
431 program_id as *const _ as *const u8,
432 &mut bytes as *mut _ as *mut u8,
433 )
434 };
435 match result {
436 // SAFETY: The syscall has initialized the bytes.
437 SUCCESS => Ok(unsafe { bytes.assume_init() }),
438 _ => Err(result.into()),
439 }
440 }
441 }
442}