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