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