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}