Skip to main content

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}