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}