lib.rs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. #![no_std]
  2. #[cfg(feature = "const")]
  3. #[doc(hidden)]
  4. // Re-export dependencies used in macros.
  5. pub mod reexport {
  6. pub use pinocchio::pubkey::Pubkey;
  7. }
  8. #[cfg(feature = "const")]
  9. use const_crypto::{bs58::decode_pubkey, sha2::Sha256};
  10. use core::mem::MaybeUninit;
  11. use pinocchio::pubkey::{Pubkey, MAX_SEEDS, PDA_MARKER};
  12. #[cfg(target_os = "solana")]
  13. use pinocchio::syscalls::sol_sha256;
  14. /// Derive a [program address][pda] from the given seeds, optional bump and
  15. /// program id.
  16. ///
  17. /// [pda]: https://solana.com/docs/core/pda
  18. ///
  19. /// In general, the derivation uses an optional bump (byte) value to ensure a
  20. /// valid PDA (off-curve) is generated. Even when a program stores a bump to
  21. /// derive a program address, it is necessary to use the
  22. /// [`pinocchio::pubkey::create_program_address`] to validate the derivation. In
  23. /// most cases, the program has the correct seeds for the derivation, so it would
  24. /// be sufficient to just perform the derivation and compare it against the
  25. /// expected resulting address.
  26. ///
  27. /// This function avoids the cost of the `create_program_address` syscall
  28. /// (`1500` compute units) by directly computing the derived address
  29. /// calculating the hash of the seeds, bump and program id using the
  30. /// `sol_sha256` syscall.
  31. ///
  32. /// # Important
  33. ///
  34. /// This function differs from [`pinocchio::pubkey::create_program_address`] in that
  35. /// it does not perform a validation to ensure that the derived address is a valid
  36. /// (off-curve) program derived address. It is intended for use in cases where the
  37. /// seeds, bump, and program id are known to be valid, and the caller wants to derive
  38. /// the address without incurring the cost of the `create_program_address` syscall.
  39. pub fn derive_address<const N: usize>(
  40. seeds: &[&[u8]; N],
  41. bump: Option<u8>,
  42. program_id: &Pubkey,
  43. ) -> Pubkey {
  44. const {
  45. assert!(N < MAX_SEEDS, "number of seeds must be less than MAX_SEEDS");
  46. }
  47. const UNINIT: MaybeUninit<&[u8]> = MaybeUninit::<&[u8]>::uninit();
  48. let mut data = [UNINIT; MAX_SEEDS + 2];
  49. let mut i = 0;
  50. while i < N {
  51. // SAFETY: `data` is guaranteed to have enough space for `N` seeds,
  52. // so `i` will always be within bounds.
  53. unsafe {
  54. data.get_unchecked_mut(i).write(seeds.get_unchecked(i));
  55. }
  56. i += 1;
  57. }
  58. // TODO: replace this with `as_slice` when the MSRV is upgraded
  59. // to `1.84.0+`.
  60. let bump_seed = [bump.unwrap_or_default()];
  61. // SAFETY: `data` is guaranteed to have enough space for `MAX_SEEDS + 2`
  62. // elements, and `MAX_SEEDS` is as large as `N`.
  63. unsafe {
  64. if bump.is_some() {
  65. data.get_unchecked_mut(i).write(&bump_seed);
  66. i += 1;
  67. }
  68. data.get_unchecked_mut(i).write(program_id.as_ref());
  69. data.get_unchecked_mut(i + 1).write(PDA_MARKER.as_ref());
  70. }
  71. #[cfg(target_os = "solana")]
  72. {
  73. let mut pda = MaybeUninit::<[u8; 32]>::uninit();
  74. // SAFETY: `data` has `i + 2` elements initialized.
  75. unsafe {
  76. sol_sha256(
  77. data.as_ptr() as *const u8,
  78. (i + 2) as u64,
  79. pda.as_mut_ptr() as *mut u8,
  80. );
  81. }
  82. // SAFETY: `pda` has been initialized by the syscall.
  83. unsafe { pda.assume_init() }
  84. }
  85. #[cfg(not(target_os = "solana"))]
  86. unreachable!("deriving a pda is only available on target `solana`");
  87. }
  88. /// Derive a [program address][pda] from the given seeds, optional bump and
  89. /// program id.
  90. ///
  91. /// [pda]: https://solana.com/docs/core/pda
  92. ///
  93. /// In general, the derivation uses an optional bump (byte) value to ensure a
  94. /// valid PDA (off-curve) is generated.
  95. ///
  96. /// This function is intended for use in `const` contexts - i.e., the seeds and
  97. /// bump are known at compile time and the program id is also a constant. It avoids
  98. /// the cost of the `create_program_address` syscall (`1500` compute units) by
  99. /// directly computing the derived address using the SHA-256 hash of the seeds,
  100. /// bump and program id.
  101. ///
  102. /// # Important
  103. ///
  104. /// This function differs from [`pinocchio::pubkey::create_program_address`] in that
  105. /// it does not perform a validation to ensure that the derived address is a valid
  106. /// (off-curve) program derived address. It is intended for use in cases where the
  107. /// seeds, bump, and program id are known to be valid, and the caller wants to derive
  108. /// the address without incurring the cost of the `create_program_address` syscall.
  109. ///
  110. /// This function is a compile-time constant version of [`derive_address`].
  111. #[cfg(feature = "const")]
  112. pub const fn derive_address_const<const N: usize>(
  113. seeds: &[&[u8]; N],
  114. bump: Option<u8>,
  115. program_id: &Pubkey,
  116. ) -> Pubkey {
  117. const {
  118. assert!(N < MAX_SEEDS, "number of seeds must be less than MAX_SEEDS");
  119. }
  120. let mut hasher = Sha256::new();
  121. let mut i = 0;
  122. while i < seeds.len() {
  123. hasher = hasher.update(seeds[i]);
  124. i += 1;
  125. }
  126. // TODO: replace this with `is_some` when the MSRV is upgraded
  127. // to `1.84.0+`.
  128. if let Some(bump) = bump {
  129. hasher
  130. .update(&[bump])
  131. .update(program_id)
  132. .update(PDA_MARKER)
  133. .finalize()
  134. } else {
  135. hasher.update(program_id).update(PDA_MARKER).finalize()
  136. }
  137. }
  138. /// Convenience macro to define a static `Pubkey` value.
  139. #[cfg(feature = "const")]
  140. #[macro_export]
  141. macro_rules! pubkey {
  142. ( $id:literal ) => {
  143. $crate::from_str($id)
  144. };
  145. }
  146. /// Convenience macro to define a static `Pubkey` value representing the program ID.
  147. ///
  148. /// This macro also defines a helper function to check whether a given pubkey is
  149. /// equal to the program ID.
  150. #[cfg(feature = "const")]
  151. #[macro_export]
  152. macro_rules! declare_id {
  153. ( $id:expr ) => {
  154. use $crate::reexport::Pubkey;
  155. #[doc = "The constant program ID."]
  156. pub const ID: Pubkey = $crate::from_str($id);
  157. #[doc = "Returns `true` if given pubkey is the program ID."]
  158. #[inline]
  159. pub fn check_id(id: &Pubkey) -> bool {
  160. id == &ID
  161. }
  162. #[doc = "Returns the program ID."]
  163. #[inline]
  164. pub const fn id() -> Pubkey {
  165. ID
  166. }
  167. };
  168. }
  169. /// Create a `Pubkey` from a `&str`.
  170. #[cfg(feature = "const")]
  171. #[inline(always)]
  172. pub const fn from_str(value: &str) -> Pubkey {
  173. decode_pubkey(value)
  174. }