solana_pubkey/lib.rs
1//! Solana account addresses.
2#![no_std]
3#![cfg_attr(docsrs, feature(doc_auto_cfg))]
4#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
5#![allow(clippy::arithmetic_side_effects)]
6
7#[cfg(any(feature = "std", target_arch = "wasm32"))]
8extern crate std;
9#[cfg(feature = "dev-context-only-utils")]
10use arbitrary::Arbitrary;
11#[cfg(feature = "bytemuck")]
12use bytemuck_derive::{Pod, Zeroable};
13#[cfg(feature = "serde")]
14use serde_derive::{Deserialize, Serialize};
15#[cfg(any(feature = "std", target_arch = "wasm32"))]
16use std::vec::Vec;
17#[cfg(feature = "borsh")]
18use {
19 borsh::{BorshDeserialize, BorshSchema, BorshSerialize},
20 std::string::ToString,
21};
22use {
23 core::{
24 array,
25 convert::{Infallible, TryFrom},
26 fmt, mem,
27 str::{from_utf8, FromStr},
28 },
29 num_traits::{FromPrimitive, ToPrimitive},
30 solana_decode_error::DecodeError,
31};
32#[cfg(target_arch = "wasm32")]
33use {
34 js_sys::{Array, Uint8Array},
35 wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue},
36};
37#[cfg(target_os = "solana")]
38pub mod syscalls;
39
40/// Number of bytes in a pubkey
41pub const PUBKEY_BYTES: usize = 32;
42/// maximum length of derived `Pubkey` seed
43pub const MAX_SEED_LEN: usize = 32;
44/// Maximum number of seeds
45pub const MAX_SEEDS: usize = 16;
46/// Maximum string length of a base58 encoded pubkey
47const MAX_BASE58_LEN: usize = 44;
48
49#[cfg(any(target_os = "solana", feature = "sha2", feature = "curve25519"))]
50const PDA_MARKER: &[u8; 21] = b"ProgramDerivedAddress";
51
52/// Copied from `solana_program::entrypoint::SUCCESS`
53/// to avoid a `solana_program` dependency
54#[cfg(target_os = "solana")]
55const SUCCESS: u64 = 0;
56
57// Use strum when testing to ensure our FromPrimitive
58// impl is exhaustive
59#[cfg_attr(test, derive(strum_macros::FromRepr, strum_macros::EnumIter))]
60#[cfg_attr(feature = "serde", derive(Serialize))]
61#[derive(Debug, Clone, PartialEq, Eq)]
62pub enum PubkeyError {
63 /// Length of the seed is too long for address generation
64 MaxSeedLengthExceeded,
65 InvalidSeeds,
66 IllegalOwner,
67}
68
69impl ToPrimitive for PubkeyError {
70 #[inline]
71 fn to_i64(&self) -> Option<i64> {
72 Some(match *self {
73 PubkeyError::MaxSeedLengthExceeded => PubkeyError::MaxSeedLengthExceeded as i64,
74 PubkeyError::InvalidSeeds => PubkeyError::InvalidSeeds as i64,
75 PubkeyError::IllegalOwner => PubkeyError::IllegalOwner as i64,
76 })
77 }
78 #[inline]
79 fn to_u64(&self) -> Option<u64> {
80 self.to_i64().map(|x| x as u64)
81 }
82}
83
84impl FromPrimitive for PubkeyError {
85 #[inline]
86 fn from_i64(n: i64) -> Option<Self> {
87 if n == PubkeyError::MaxSeedLengthExceeded as i64 {
88 Some(PubkeyError::MaxSeedLengthExceeded)
89 } else if n == PubkeyError::InvalidSeeds as i64 {
90 Some(PubkeyError::InvalidSeeds)
91 } else if n == PubkeyError::IllegalOwner as i64 {
92 Some(PubkeyError::IllegalOwner)
93 } else {
94 None
95 }
96 }
97 #[inline]
98 fn from_u64(n: u64) -> Option<Self> {
99 Self::from_i64(n as i64)
100 }
101}
102
103#[cfg(feature = "std")]
104impl std::error::Error for PubkeyError {}
105
106impl fmt::Display for PubkeyError {
107 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
108 match self {
109 PubkeyError::MaxSeedLengthExceeded => {
110 f.write_str("Length of the seed is too long for address generation")
111 }
112 PubkeyError::InvalidSeeds => {
113 f.write_str("Provided seeds do not result in a valid address")
114 }
115 PubkeyError::IllegalOwner => f.write_str("Provided owner is not allowed"),
116 }
117 }
118}
119
120impl<T> DecodeError<T> for PubkeyError {
121 fn type_of() -> &'static str {
122 "PubkeyError"
123 }
124}
125impl From<u64> for PubkeyError {
126 fn from(error: u64) -> Self {
127 match error {
128 0 => PubkeyError::MaxSeedLengthExceeded,
129 1 => PubkeyError::InvalidSeeds,
130 2 => PubkeyError::IllegalOwner,
131 _ => panic!("Unsupported PubkeyError"),
132 }
133 }
134}
135
136/// The address of a [Solana account][acc].
137///
138/// Some account addresses are [ed25519] public keys, with corresponding secret
139/// keys that are managed off-chain. Often, though, account addresses do not
140/// have corresponding secret keys — as with [_program derived
141/// addresses_][pdas] — or the secret key is not relevant to the operation
142/// of a program, and may have even been disposed of. As running Solana programs
143/// can not safely create or manage secret keys, the full [`Keypair`] is not
144/// defined in `solana-program` but in `solana-sdk`.
145///
146/// [acc]: https://solana.com/docs/core/accounts
147/// [ed25519]: https://ed25519.cr.yp.to/
148/// [pdas]: https://solana.com/docs/core/cpi#program-derived-addresses
149/// [`Keypair`]: https://docs.rs/solana-sdk/latest/solana_sdk/signer/keypair/struct.Keypair.html
150#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
151#[repr(transparent)]
152#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))]
153#[cfg_attr(
154 feature = "borsh",
155 derive(BorshSerialize, BorshDeserialize),
156 borsh(crate = "borsh")
157)]
158#[cfg_attr(all(feature = "borsh", feature = "std"), derive(BorshSchema))]
159#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
160#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
161#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
162#[cfg_attr(feature = "dev-context-only-utils", derive(Arbitrary))]
163pub struct Pubkey(pub(crate) [u8; 32]);
164
165impl solana_sanitize::Sanitize for Pubkey {}
166
167// Use strum when testing to ensure our FromPrimitive
168// impl is exhaustive
169#[cfg_attr(test, derive(strum_macros::FromRepr, strum_macros::EnumIter))]
170#[cfg_attr(feature = "serde", derive(Serialize))]
171#[derive(Debug, Clone, PartialEq, Eq)]
172pub enum ParsePubkeyError {
173 WrongSize,
174 Invalid,
175}
176
177impl ToPrimitive for ParsePubkeyError {
178 #[inline]
179 fn to_i64(&self) -> Option<i64> {
180 Some(match *self {
181 ParsePubkeyError::WrongSize => ParsePubkeyError::WrongSize as i64,
182 ParsePubkeyError::Invalid => ParsePubkeyError::Invalid as i64,
183 })
184 }
185 #[inline]
186 fn to_u64(&self) -> Option<u64> {
187 self.to_i64().map(|x| x as u64)
188 }
189}
190
191impl FromPrimitive for ParsePubkeyError {
192 #[inline]
193 fn from_i64(n: i64) -> Option<Self> {
194 if n == ParsePubkeyError::WrongSize as i64 {
195 Some(ParsePubkeyError::WrongSize)
196 } else if n == ParsePubkeyError::Invalid as i64 {
197 Some(ParsePubkeyError::Invalid)
198 } else {
199 None
200 }
201 }
202 #[inline]
203 fn from_u64(n: u64) -> Option<Self> {
204 Self::from_i64(n as i64)
205 }
206}
207
208#[cfg(feature = "std")]
209impl std::error::Error for ParsePubkeyError {}
210
211impl fmt::Display for ParsePubkeyError {
212 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
213 match self {
214 ParsePubkeyError::WrongSize => f.write_str("String is the wrong size"),
215 ParsePubkeyError::Invalid => f.write_str("Invalid Base58 string"),
216 }
217 }
218}
219
220impl From<Infallible> for ParsePubkeyError {
221 fn from(_: Infallible) -> Self {
222 unreachable!("Infallible uninhabited");
223 }
224}
225
226impl<T> DecodeError<T> for ParsePubkeyError {
227 fn type_of() -> &'static str {
228 "ParsePubkeyError"
229 }
230}
231
232impl FromStr for Pubkey {
233 type Err = ParsePubkeyError;
234
235 fn from_str(s: &str) -> Result<Self, Self::Err> {
236 if s.len() > MAX_BASE58_LEN {
237 return Err(ParsePubkeyError::WrongSize);
238 }
239 let mut bytes = [0; PUBKEY_BYTES];
240 let decoded_size = bs58::decode(s)
241 .onto(&mut bytes)
242 .map_err(|_| ParsePubkeyError::Invalid)?;
243 if decoded_size != mem::size_of::<Pubkey>() {
244 Err(ParsePubkeyError::WrongSize)
245 } else {
246 Ok(Pubkey(bytes))
247 }
248 }
249}
250
251impl From<[u8; 32]> for Pubkey {
252 #[inline]
253 fn from(from: [u8; 32]) -> Self {
254 Self(from)
255 }
256}
257
258impl TryFrom<&[u8]> for Pubkey {
259 type Error = array::TryFromSliceError;
260
261 #[inline]
262 fn try_from(pubkey: &[u8]) -> Result<Self, Self::Error> {
263 <[u8; 32]>::try_from(pubkey).map(Self::from)
264 }
265}
266
267#[cfg(any(feature = "std", target_arch = "wasm32"))]
268impl TryFrom<Vec<u8>> for Pubkey {
269 type Error = Vec<u8>;
270
271 #[inline]
272 fn try_from(pubkey: Vec<u8>) -> Result<Self, Self::Error> {
273 <[u8; 32]>::try_from(pubkey).map(Self::from)
274 }
275}
276
277impl TryFrom<&str> for Pubkey {
278 type Error = ParsePubkeyError;
279 fn try_from(s: &str) -> Result<Self, Self::Error> {
280 Pubkey::from_str(s)
281 }
282}
283
284// If target_os = "solana", then this panics so there are no dependencies.
285// When target_os != "solana", this should be opt-in so users
286// don't need the curve25519 dependency.
287#[cfg(any(target_os = "solana", feature = "curve25519"))]
288#[allow(clippy::used_underscore_binding)]
289pub fn bytes_are_curve_point<T: AsRef<[u8]>>(_bytes: T) -> bool {
290 #[cfg(not(target_os = "solana"))]
291 {
292 let Ok(compressed_edwards_y) =
293 curve25519_dalek::edwards::CompressedEdwardsY::from_slice(_bytes.as_ref())
294 else {
295 return false;
296 };
297 compressed_edwards_y.decompress().is_some()
298 }
299 #[cfg(target_os = "solana")]
300 unimplemented!();
301}
302
303impl Pubkey {
304 pub const fn new_from_array(pubkey_array: [u8; 32]) -> Self {
305 Self(pubkey_array)
306 }
307
308 /// Decode a string into a Pubkey, usable in a const context
309 pub const fn from_str_const(s: &str) -> Self {
310 let id_array = five8_const::decode_32_const(s);
311 Pubkey::new_from_array(id_array)
312 }
313
314 /// unique Pubkey for tests and benchmarks.
315 pub fn new_unique() -> Self {
316 use solana_atomic_u64::AtomicU64;
317 static I: AtomicU64 = AtomicU64::new(1);
318
319 let mut b = [0u8; 32];
320 let i = I.fetch_add(1);
321 // use big endian representation to ensure that recent unique pubkeys
322 // are always greater than less recent unique pubkeys
323 b[0..8].copy_from_slice(&i.to_be_bytes());
324 Self::from(b)
325 }
326
327 // If target_os = "solana", then the solana_sha256_hasher crate will use
328 // syscalls which bring no dependencies.
329 // When target_os != "solana", this should be opt-in so users
330 // don't need the sha2 dependency.
331 #[cfg(any(target_os = "solana", feature = "sha2"))]
332 pub fn create_with_seed(
333 base: &Pubkey,
334 seed: &str,
335 owner: &Pubkey,
336 ) -> Result<Pubkey, PubkeyError> {
337 if seed.len() > MAX_SEED_LEN {
338 return Err(PubkeyError::MaxSeedLengthExceeded);
339 }
340
341 let owner = owner.as_ref();
342 if owner.len() >= PDA_MARKER.len() {
343 let slice = &owner[owner.len() - PDA_MARKER.len()..];
344 if slice == PDA_MARKER {
345 return Err(PubkeyError::IllegalOwner);
346 }
347 }
348 let hash = solana_sha256_hasher::hashv(&[base.as_ref(), seed.as_ref(), owner]);
349 Ok(Pubkey::from(hash.to_bytes()))
350 }
351
352 /// Find a valid [program derived address][pda] and its corresponding bump seed.
353 ///
354 /// [pda]: https://solana.com/docs/core/cpi#program-derived-addresses
355 ///
356 /// Program derived addresses (PDAs) are account keys that only the program,
357 /// `program_id`, has the authority to sign. The address is of the same form
358 /// as a Solana `Pubkey`, except they are ensured to not be on the ed25519
359 /// curve and thus have no associated private key. When performing
360 /// cross-program invocations the program can "sign" for the key by calling
361 /// [`invoke_signed`] and passing the same seeds used to generate the
362 /// address, along with the calculated _bump seed_, which this function
363 /// returns as the second tuple element. The runtime will verify that the
364 /// program associated with this address is the caller and thus authorized
365 /// to be the signer.
366 ///
367 /// [`invoke_signed`]: https://docs.rs/solana-program/latest/solana_program/program/fn.invoke_signed.html
368 ///
369 /// The `seeds` are application-specific, and must be carefully selected to
370 /// uniquely derive accounts per application requirements. It is common to
371 /// use static strings and other pubkeys as seeds.
372 ///
373 /// Because the program address must not lie on the ed25519 curve, there may
374 /// be seed and program id combinations that are invalid. For this reason,
375 /// an extra seed (the bump seed) is calculated that results in a
376 /// point off the curve. The bump seed must be passed as an additional seed
377 /// when calling `invoke_signed`.
378 ///
379 /// The processes of finding a valid program address is by trial and error,
380 /// and even though it is deterministic given a set of inputs it can take a
381 /// variable amount of time to succeed across different inputs. This means
382 /// that when called from an on-chain program it may incur a variable amount
383 /// of the program's compute budget. Programs that are meant to be very
384 /// performant may not want to use this function because it could take a
385 /// considerable amount of time. Programs that are already at risk
386 /// of exceeding their compute budget should call this with care since
387 /// there is a chance that the program's budget may be occasionally
388 /// and unpredictably exceeded.
389 ///
390 /// As all account addresses accessed by an on-chain Solana program must be
391 /// explicitly passed to the program, it is typical for the PDAs to be
392 /// derived in off-chain client programs, avoiding the compute cost of
393 /// generating the address on-chain. The address may or may not then be
394 /// verified by re-deriving it on-chain, depending on the requirements of
395 /// the program. This verification may be performed without the overhead of
396 /// re-searching for the bump key by using the [`create_program_address`]
397 /// function.
398 ///
399 /// [`create_program_address`]: Pubkey::create_program_address
400 ///
401 /// **Warning**: Because of the way the seeds are hashed there is a potential
402 /// for program address collisions for the same program id. The seeds are
403 /// hashed sequentially which means that seeds {"abcdef"}, {"abc", "def"},
404 /// and {"ab", "cd", "ef"} will all result in the same program address given
405 /// the same program id. Since the chance of collision is local to a given
406 /// program id, the developer of that program must take care to choose seeds
407 /// that do not collide with each other. For seed schemes that are susceptible
408 /// to this type of hash collision, a common remedy is to insert separators
409 /// between seeds, e.g. transforming {"abc", "def"} into {"abc", "-", "def"}.
410 ///
411 /// # Panics
412 ///
413 /// Panics in the statistically improbable event that a bump seed could not be
414 /// found. Use [`try_find_program_address`] to handle this case.
415 ///
416 /// [`try_find_program_address`]: Pubkey::try_find_program_address
417 ///
418 /// Panics if any of the following are true:
419 ///
420 /// - the number of provided seeds is greater than, _or equal to_, [`MAX_SEEDS`],
421 /// - any individual seed's length is greater than [`MAX_SEED_LEN`].
422 ///
423 /// # Examples
424 ///
425 /// This example illustrates a simple case of creating a "vault" account
426 /// which is derived from the payer account, but owned by an on-chain
427 /// program. The program derived address is derived in an off-chain client
428 /// program, which invokes an on-chain Solana program that uses the address
429 /// to create a new account owned and controlled by the program itself.
430 ///
431 /// By convention, the on-chain program will be compiled for use in two
432 /// different contexts: both on-chain, to interpret a custom program
433 /// instruction as a Solana transaction; and off-chain, as a library, so
434 /// that clients can share the instruction data structure, constructors, and
435 /// other common code.
436 ///
437 /// First the on-chain Solana program:
438 ///
439 /// ```
440 /// # use borsh::{BorshSerialize, BorshDeserialize};
441 /// # use solana_pubkey::Pubkey;
442 /// # use solana_program::{
443 /// # entrypoint::ProgramResult,
444 /// # program::invoke_signed,
445 /// # system_instruction,
446 /// # account_info::{
447 /// # AccountInfo,
448 /// # next_account_info,
449 /// # },
450 /// # };
451 /// // The custom instruction processed by our program. It includes the
452 /// // PDA's bump seed, which is derived by the client program. This
453 /// // definition is also imported into the off-chain client program.
454 /// // The computed address of the PDA will be passed to this program via
455 /// // the `accounts` vector of the `Instruction` type.
456 /// #[derive(BorshSerialize, BorshDeserialize, Debug)]
457 /// # #[borsh(crate = "borsh")]
458 /// pub struct InstructionData {
459 /// pub vault_bump_seed: u8,
460 /// pub lamports: u64,
461 /// }
462 ///
463 /// // The size in bytes of a vault account. The client program needs
464 /// // this information to calculate the quantity of lamports necessary
465 /// // to pay for the account's rent.
466 /// pub static VAULT_ACCOUNT_SIZE: u64 = 1024;
467 ///
468 /// // The entrypoint of the on-chain program, as provided to the
469 /// // `entrypoint!` macro.
470 /// fn process_instruction(
471 /// program_id: &Pubkey,
472 /// accounts: &[AccountInfo],
473 /// instruction_data: &[u8],
474 /// ) -> ProgramResult {
475 /// let account_info_iter = &mut accounts.iter();
476 /// let payer = next_account_info(account_info_iter)?;
477 /// // The vault PDA, derived from the payer's address
478 /// let vault = next_account_info(account_info_iter)?;
479 ///
480 /// let mut instruction_data = instruction_data;
481 /// let instr = InstructionData::deserialize(&mut instruction_data)?;
482 /// let vault_bump_seed = instr.vault_bump_seed;
483 /// let lamports = instr.lamports;
484 /// let vault_size = VAULT_ACCOUNT_SIZE;
485 ///
486 /// // Invoke the system program to create an account while virtually
487 /// // signing with the vault PDA, which is owned by this caller program.
488 /// invoke_signed(
489 /// &system_instruction::create_account(
490 /// &payer.key,
491 /// &vault.key,
492 /// lamports,
493 /// vault_size,
494 /// &program_id,
495 /// ),
496 /// &[
497 /// payer.clone(),
498 /// vault.clone(),
499 /// ],
500 /// // A slice of seed slices, each seed slice being the set
501 /// // of seeds used to generate one of the PDAs required by the
502 /// // callee program, the final seed being a single-element slice
503 /// // containing the `u8` bump seed.
504 /// &[
505 /// &[
506 /// b"vault",
507 /// payer.key.as_ref(),
508 /// &[vault_bump_seed],
509 /// ],
510 /// ]
511 /// )?;
512 ///
513 /// Ok(())
514 /// }
515 /// ```
516 ///
517 /// The client program:
518 ///
519 /// ```
520 /// # use borsh::{BorshSerialize, BorshDeserialize};
521 /// # use solana_program::example_mocks::{solana_sdk, solana_rpc_client};
522 /// # use solana_pubkey::Pubkey;
523 /// # use solana_program::{
524 /// # instruction::Instruction,
525 /// # hash::Hash,
526 /// # instruction::AccountMeta,
527 /// # system_program,
528 /// # };
529 /// # use solana_sdk::{
530 /// # signature::Keypair,
531 /// # signature::{Signer, Signature},
532 /// # transaction::Transaction,
533 /// # };
534 /// # use solana_rpc_client::rpc_client::RpcClient;
535 /// # use std::convert::TryFrom;
536 /// # use anyhow::Result;
537 /// #
538 /// # #[derive(BorshSerialize, BorshDeserialize, Debug)]
539 /// # #[borsh(crate = "borsh")]
540 /// # struct InstructionData {
541 /// # pub vault_bump_seed: u8,
542 /// # pub lamports: u64,
543 /// # }
544 /// #
545 /// # pub static VAULT_ACCOUNT_SIZE: u64 = 1024;
546 /// #
547 /// fn create_vault_account(
548 /// client: &RpcClient,
549 /// program_id: Pubkey,
550 /// payer: &Keypair,
551 /// ) -> Result<()> {
552 /// // Derive the PDA from the payer account, a string representing the unique
553 /// // purpose of the account ("vault"), and the address of our on-chain program.
554 /// let (vault_pubkey, vault_bump_seed) = Pubkey::find_program_address(
555 /// &[b"vault", payer.pubkey().as_ref()],
556 /// &program_id
557 /// );
558 ///
559 /// // Get the amount of lamports needed to pay for the vault's rent
560 /// let vault_account_size = usize::try_from(VAULT_ACCOUNT_SIZE)?;
561 /// let lamports = client.get_minimum_balance_for_rent_exemption(vault_account_size)?;
562 ///
563 /// // The on-chain program's instruction data, imported from that program's crate.
564 /// let instr_data = InstructionData {
565 /// vault_bump_seed,
566 /// lamports,
567 /// };
568 ///
569 /// // The accounts required by both our on-chain program and the system program's
570 /// // `create_account` instruction, including the vault's address.
571 /// let accounts = vec![
572 /// AccountMeta::new(payer.pubkey(), true),
573 /// AccountMeta::new(vault_pubkey, false),
574 /// AccountMeta::new(system_program::ID, false),
575 /// ];
576 ///
577 /// // Create the instruction by serializing our instruction data via borsh
578 /// let instruction = Instruction::new_with_borsh(
579 /// program_id,
580 /// &instr_data,
581 /// accounts,
582 /// );
583 ///
584 /// let blockhash = client.get_latest_blockhash()?;
585 ///
586 /// let transaction = Transaction::new_signed_with_payer(
587 /// &[instruction],
588 /// Some(&payer.pubkey()),
589 /// &[payer],
590 /// blockhash,
591 /// );
592 ///
593 /// client.send_and_confirm_transaction(&transaction)?;
594 ///
595 /// Ok(())
596 /// }
597 /// # let program_id = Pubkey::new_unique();
598 /// # let payer = Keypair::new();
599 /// # let client = RpcClient::new(String::new());
600 /// #
601 /// # create_vault_account(&client, program_id, &payer)?;
602 /// #
603 /// # Ok::<(), anyhow::Error>(())
604 /// ```
605 // If target_os = "solana", then the function will use
606 // syscalls which bring no dependencies.
607 // When target_os != "solana", this should be opt-in so users
608 // don't need the curve25519 dependency.
609 #[cfg(any(target_os = "solana", feature = "curve25519"))]
610 pub fn find_program_address(seeds: &[&[u8]], program_id: &Pubkey) -> (Pubkey, u8) {
611 Self::try_find_program_address(seeds, program_id)
612 .unwrap_or_else(|| panic!("Unable to find a viable program address bump seed"))
613 }
614
615 /// Find a valid [program derived address][pda] and its corresponding bump seed.
616 ///
617 /// [pda]: https://solana.com/docs/core/cpi#program-derived-addresses
618 ///
619 /// The only difference between this method and [`find_program_address`]
620 /// is that this one returns `None` in the statistically improbable event
621 /// that a bump seed cannot be found; or if any of `find_program_address`'s
622 /// preconditions are violated.
623 ///
624 /// See the documentation for [`find_program_address`] for a full description.
625 ///
626 /// [`find_program_address`]: Pubkey::find_program_address
627 // If target_os = "solana", then the function will use
628 // syscalls which bring no dependencies.
629 // When target_os != "solana", this should be opt-in so users
630 // don't need the curve25519 dependency.
631 #[cfg(any(target_os = "solana", feature = "curve25519"))]
632 #[allow(clippy::same_item_push)]
633 pub fn try_find_program_address(seeds: &[&[u8]], program_id: &Pubkey) -> Option<(Pubkey, u8)> {
634 // Perform the calculation inline, calling this from within a program is
635 // not supported
636 #[cfg(not(target_os = "solana"))]
637 {
638 let mut bump_seed = [u8::MAX];
639 for _ in 0..u8::MAX {
640 {
641 let mut seeds_with_bump = seeds.to_vec();
642 seeds_with_bump.push(&bump_seed);
643 match Self::create_program_address(&seeds_with_bump, program_id) {
644 Ok(address) => return Some((address, bump_seed[0])),
645 Err(PubkeyError::InvalidSeeds) => (),
646 _ => break,
647 }
648 }
649 bump_seed[0] -= 1;
650 }
651 None
652 }
653 // Call via a system call to perform the calculation
654 #[cfg(target_os = "solana")]
655 {
656 let mut bytes = [0; 32];
657 let mut bump_seed = u8::MAX;
658 let result = unsafe {
659 crate::syscalls::sol_try_find_program_address(
660 seeds as *const _ as *const u8,
661 seeds.len() as u64,
662 program_id as *const _ as *const u8,
663 &mut bytes as *mut _ as *mut u8,
664 &mut bump_seed as *mut _ as *mut u8,
665 )
666 };
667 match result {
668 SUCCESS => Some((Pubkey::from(bytes), bump_seed)),
669 _ => None,
670 }
671 }
672 }
673
674 /// Create a valid [program derived address][pda] without searching for a bump seed.
675 ///
676 /// [pda]: https://solana.com/docs/core/cpi#program-derived-addresses
677 ///
678 /// Because this function does not create a bump seed, it may unpredictably
679 /// return an error for any given set of seeds and is not generally suitable
680 /// for creating program derived addresses.
681 ///
682 /// However, it can be used for efficiently verifying that a set of seeds plus
683 /// bump seed generated by [`find_program_address`] derives a particular
684 /// address as expected. See the example for details.
685 ///
686 /// See the documentation for [`find_program_address`] for a full description
687 /// of program derived addresses and bump seeds.
688 ///
689 /// [`find_program_address`]: Pubkey::find_program_address
690 ///
691 /// # Examples
692 ///
693 /// Creating a program derived address involves iteratively searching for a
694 /// bump seed for which the derived [`Pubkey`] does not lie on the ed25519
695 /// curve. This search process is generally performed off-chain, with the
696 /// [`find_program_address`] function, after which the client passes the
697 /// bump seed to the program as instruction data.
698 ///
699 /// Depending on the application requirements, a program may wish to verify
700 /// that the set of seeds, plus the bump seed, do correctly generate an
701 /// expected address.
702 ///
703 /// The verification is performed by appending to the other seeds one
704 /// additional seed slice that contains the single `u8` bump seed, calling
705 /// `create_program_address`, checking that the return value is `Ok`, and
706 /// that the returned `Pubkey` has the expected value.
707 ///
708 /// ```
709 /// # use solana_pubkey::Pubkey;
710 /// # let program_id = Pubkey::new_unique();
711 /// let (expected_pda, bump_seed) = Pubkey::find_program_address(&[b"vault"], &program_id);
712 /// let actual_pda = Pubkey::create_program_address(&[b"vault", &[bump_seed]], &program_id)?;
713 /// assert_eq!(expected_pda, actual_pda);
714 /// # Ok::<(), anyhow::Error>(())
715 /// ```
716 // If target_os = "solana", then the function will use
717 // syscalls which bring no dependencies.
718 // When target_os != "solana", this should be opt-in so users
719 // don't need the curve225519 dep.
720 #[cfg(any(target_os = "solana", feature = "curve25519"))]
721 pub fn create_program_address(
722 seeds: &[&[u8]],
723 program_id: &Pubkey,
724 ) -> Result<Pubkey, PubkeyError> {
725 if seeds.len() > MAX_SEEDS {
726 return Err(PubkeyError::MaxSeedLengthExceeded);
727 }
728 for seed in seeds.iter() {
729 if seed.len() > MAX_SEED_LEN {
730 return Err(PubkeyError::MaxSeedLengthExceeded);
731 }
732 }
733
734 // Perform the calculation inline, calling this from within a program is
735 // not supported
736 #[cfg(not(target_os = "solana"))]
737 {
738 let mut hasher = solana_sha256_hasher::Hasher::default();
739 for seed in seeds.iter() {
740 hasher.hash(seed);
741 }
742 hasher.hashv(&[program_id.as_ref(), PDA_MARKER]);
743 let hash = hasher.result();
744
745 if bytes_are_curve_point(hash) {
746 return Err(PubkeyError::InvalidSeeds);
747 }
748
749 Ok(Pubkey::from(hash.to_bytes()))
750 }
751 // Call via a system call to perform the calculation
752 #[cfg(target_os = "solana")]
753 {
754 let mut bytes = [0; 32];
755 let result = unsafe {
756 crate::syscalls::sol_create_program_address(
757 seeds as *const _ as *const u8,
758 seeds.len() as u64,
759 program_id as *const _ as *const u8,
760 &mut bytes as *mut _ as *mut u8,
761 )
762 };
763 match result {
764 SUCCESS => Ok(Pubkey::from(bytes)),
765 _ => Err(result.into()),
766 }
767 }
768 }
769
770 pub const fn to_bytes(self) -> [u8; 32] {
771 self.0
772 }
773
774 // If target_os = "solana", then this panics so there are no dependencies.
775 // When target_os != "solana", this should be opt-in so users
776 // don't need the curve25519 dependency.
777 #[cfg(any(target_os = "solana", feature = "curve25519"))]
778 pub fn is_on_curve(&self) -> bool {
779 bytes_are_curve_point(self)
780 }
781
782 /// Log a `Pubkey` from a program
783 pub fn log(&self) {
784 #[cfg(target_os = "solana")]
785 unsafe {
786 crate::syscalls::sol_log_pubkey(self.as_ref() as *const _ as *const u8)
787 };
788
789 #[cfg(all(not(target_os = "solana"), feature = "std"))]
790 std::println!("{}", std::string::ToString::to_string(&self));
791 }
792}
793
794impl AsRef<[u8]> for Pubkey {
795 fn as_ref(&self) -> &[u8] {
796 &self.0[..]
797 }
798}
799
800impl AsMut<[u8]> for Pubkey {
801 fn as_mut(&mut self) -> &mut [u8] {
802 &mut self.0[..]
803 }
804}
805
806fn write_as_base58(f: &mut fmt::Formatter, p: &Pubkey) -> fmt::Result {
807 let mut out = [0u8; MAX_BASE58_LEN];
808 let out_slice: &mut [u8] = &mut out;
809 // This will never fail because the only possible error is BufferTooSmall,
810 // and we will never call it with too small a buffer.
811 let len = bs58::encode(p.0).onto(out_slice).unwrap();
812 let as_str = from_utf8(&out[..len]).unwrap();
813 f.write_str(as_str)
814}
815
816impl fmt::Debug for Pubkey {
817 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
818 write_as_base58(f, self)
819 }
820}
821
822impl fmt::Display for Pubkey {
823 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
824 write_as_base58(f, self)
825 }
826}
827
828#[cfg(feature = "borsh")]
829impl borsh0_10::de::BorshDeserialize for Pubkey {
830 fn deserialize_reader<R: borsh0_10::maybestd::io::Read>(
831 reader: &mut R,
832 ) -> Result<Self, borsh0_10::maybestd::io::Error> {
833 Ok(Self(borsh0_10::BorshDeserialize::deserialize_reader(
834 reader,
835 )?))
836 }
837}
838
839#[cfg(feature = "borsh")]
840macro_rules! impl_borsh_schema {
841 ($borsh:ident) => {
842 impl $borsh::BorshSchema for Pubkey
843 where
844 [u8; 32]: $borsh::BorshSchema,
845 {
846 fn declaration() -> $borsh::schema::Declaration {
847 std::string::String::from("Pubkey")
848 }
849 fn add_definitions_recursively(
850 definitions: &mut $borsh::maybestd::collections::HashMap<
851 $borsh::schema::Declaration,
852 $borsh::schema::Definition,
853 >,
854 ) {
855 let fields = $borsh::schema::Fields::UnnamedFields(<[_]>::into_vec(
856 $borsh::maybestd::boxed::Box::new([
857 <[u8; 32] as $borsh::BorshSchema>::declaration(),
858 ]),
859 ));
860 let definition = $borsh::schema::Definition::Struct { fields };
861 <Self as $borsh::BorshSchema>::add_definition(
862 <Self as $borsh::BorshSchema>::declaration(),
863 definition,
864 definitions,
865 );
866 <[u8; 32] as $borsh::BorshSchema>::add_definitions_recursively(definitions);
867 }
868 }
869 };
870}
871#[cfg(feature = "borsh")]
872impl_borsh_schema!(borsh0_10);
873
874#[cfg(feature = "borsh")]
875macro_rules! impl_borsh_serialize {
876 ($borsh:ident) => {
877 impl $borsh::ser::BorshSerialize for Pubkey {
878 fn serialize<W: $borsh::maybestd::io::Write>(
879 &self,
880 writer: &mut W,
881 ) -> ::core::result::Result<(), $borsh::maybestd::io::Error> {
882 $borsh::BorshSerialize::serialize(&self.0, writer)?;
883 Ok(())
884 }
885 }
886 };
887}
888#[cfg(feature = "borsh")]
889impl_borsh_serialize!(borsh0_10);
890
891#[cfg(all(target_arch = "wasm32", feature = "curve25519"))]
892fn js_value_to_seeds_vec(array_of_uint8_arrays: &[JsValue]) -> Result<Vec<Vec<u8>>, JsValue> {
893 let vec_vec_u8 = array_of_uint8_arrays
894 .iter()
895 .filter_map(|u8_array| {
896 u8_array
897 .dyn_ref::<Uint8Array>()
898 .map(|u8_array| u8_array.to_vec())
899 })
900 .collect::<Vec<_>>();
901
902 if vec_vec_u8.len() != array_of_uint8_arrays.len() {
903 Err("Invalid Array of Uint8Arrays".into())
904 } else {
905 Ok(vec_vec_u8)
906 }
907}
908
909#[cfg(target_arch = "wasm32")]
910fn display_to_jsvalue<T: fmt::Display>(display: T) -> JsValue {
911 std::string::ToString::to_string(&display).into()
912}
913
914#[allow(non_snake_case)]
915#[cfg(target_arch = "wasm32")]
916#[wasm_bindgen]
917impl Pubkey {
918 /// Create a new Pubkey object
919 ///
920 /// * `value` - optional public key as a base58 encoded string, `Uint8Array`, `[number]`
921 #[wasm_bindgen(constructor)]
922 pub fn constructor(value: JsValue) -> Result<Pubkey, JsValue> {
923 if let Some(base58_str) = value.as_string() {
924 base58_str.parse::<Pubkey>().map_err(display_to_jsvalue)
925 } else if let Some(uint8_array) = value.dyn_ref::<Uint8Array>() {
926 Pubkey::try_from(uint8_array.to_vec())
927 .map_err(|err| JsValue::from(std::format!("Invalid Uint8Array pubkey: {err:?}")))
928 } else if let Some(array) = value.dyn_ref::<Array>() {
929 let mut bytes = std::vec![];
930 let iterator = js_sys::try_iter(&array.values())?.expect("array to be iterable");
931 for x in iterator {
932 let x = x?;
933
934 if let Some(n) = x.as_f64() {
935 if n >= 0. && n <= 255. {
936 bytes.push(n as u8);
937 continue;
938 }
939 }
940 return Err(std::format!("Invalid array argument: {:?}", x).into());
941 }
942 Pubkey::try_from(bytes)
943 .map_err(|err| JsValue::from(std::format!("Invalid Array pubkey: {err:?}")))
944 } else if value.is_undefined() {
945 Ok(Pubkey::default())
946 } else {
947 Err("Unsupported argument".into())
948 }
949 }
950
951 /// Return the base58 string representation of the public key
952 pub fn toString(&self) -> std::string::String {
953 std::string::ToString::to_string(self)
954 }
955
956 /// Check if a `Pubkey` is on the ed25519 curve.
957 #[cfg(feature = "curve25519")]
958 pub fn isOnCurve(&self) -> bool {
959 self.is_on_curve()
960 }
961
962 /// Checks if two `Pubkey`s are equal
963 pub fn equals(&self, other: &Pubkey) -> bool {
964 self == other
965 }
966
967 /// Return the `Uint8Array` representation of the public key
968 pub fn toBytes(&self) -> std::boxed::Box<[u8]> {
969 self.0.clone().into()
970 }
971
972 /// Derive a Pubkey from another Pubkey, string seed, and a program id
973 #[cfg(feature = "sha2")]
974 pub fn createWithSeed(base: &Pubkey, seed: &str, owner: &Pubkey) -> Result<Pubkey, JsValue> {
975 Pubkey::create_with_seed(base, seed, owner).map_err(display_to_jsvalue)
976 }
977
978 /// Derive a program address from seeds and a program id
979 #[cfg(feature = "curve25519")]
980 pub fn createProgramAddress(
981 seeds: std::boxed::Box<[JsValue]>,
982 program_id: &Pubkey,
983 ) -> Result<Pubkey, JsValue> {
984 let seeds_vec = js_value_to_seeds_vec(&seeds)?;
985 let seeds_slice = seeds_vec
986 .iter()
987 .map(|seed| seed.as_slice())
988 .collect::<Vec<_>>();
989
990 Pubkey::create_program_address(seeds_slice.as_slice(), program_id)
991 .map_err(display_to_jsvalue)
992 }
993
994 /// Find a valid program address
995 ///
996 /// Returns:
997 /// * `[PubKey, number]` - the program address and bump seed
998 #[cfg(feature = "curve25519")]
999 pub fn findProgramAddress(
1000 seeds: std::boxed::Box<[JsValue]>,
1001 program_id: &Pubkey,
1002 ) -> Result<JsValue, JsValue> {
1003 let seeds_vec = js_value_to_seeds_vec(&seeds)?;
1004 let seeds_slice = seeds_vec
1005 .iter()
1006 .map(|seed| seed.as_slice())
1007 .collect::<Vec<_>>();
1008
1009 let (address, bump_seed) = Pubkey::find_program_address(seeds_slice.as_slice(), program_id);
1010
1011 let result = Array::new_with_length(2);
1012 result.set(0, address.into());
1013 result.set(1, bump_seed.into());
1014 Ok(result.into())
1015 }
1016}
1017
1018/// Convenience macro to declare a static public key and functions to interact with it.
1019///
1020/// Input: a single literal base58 string representation of a program's ID.
1021///
1022/// # Example
1023///
1024/// ```
1025/// # // wrapper is used so that the macro invocation occurs in the item position
1026/// # // rather than in the statement position which isn't allowed.
1027/// use std::str::FromStr;
1028/// use solana_pubkey::{declare_id, Pubkey};
1029///
1030/// # mod item_wrapper {
1031/// # use solana_pubkey::declare_id;
1032/// declare_id!("My11111111111111111111111111111111111111111");
1033/// # }
1034/// # use item_wrapper::id;
1035///
1036/// let my_id = Pubkey::from_str("My11111111111111111111111111111111111111111").unwrap();
1037/// assert_eq!(id(), my_id);
1038/// ```
1039#[macro_export]
1040macro_rules! declare_id {
1041 ($address:expr) => {
1042 /// The const program ID.
1043 pub const ID: $crate::Pubkey = $crate::Pubkey::from_str_const($address);
1044
1045 /// Returns `true` if given pubkey is the program ID.
1046 // TODO make this const once `derive_const` makes it out of nightly
1047 // and we can `derive_const(PartialEq)` on `Pubkey`.
1048 pub fn check_id(id: &$crate::Pubkey) -> bool {
1049 id == &ID
1050 }
1051
1052 /// Returns the program ID.
1053 pub const fn id() -> $crate::Pubkey {
1054 ID
1055 }
1056
1057 #[cfg(test)]
1058 #[test]
1059 fn test_id() {
1060 assert!(check_id(&id()));
1061 }
1062 };
1063}
1064
1065/// Same as [`declare_id`] except that it reports that this ID has been deprecated.
1066#[macro_export]
1067macro_rules! declare_deprecated_id {
1068 ($address:expr) => {
1069 /// The const program ID.
1070 pub const ID: $crate::Pubkey = $crate::Pubkey::from_str_const($address);
1071
1072 /// Returns `true` if given pubkey is the program ID.
1073 // TODO make this const once `derive_const` makes it out of nightly
1074 // and we can `derive_const(PartialEq)` on `Pubkey`.
1075 #[deprecated()]
1076 pub fn check_id(id: &$crate::Pubkey) -> bool {
1077 id == &ID
1078 }
1079
1080 /// Returns the program ID.
1081 #[deprecated()]
1082 pub const fn id() -> $crate::Pubkey {
1083 ID
1084 }
1085
1086 #[cfg(test)]
1087 #[test]
1088 #[allow(deprecated)]
1089 fn test_id() {
1090 assert!(check_id(&id()));
1091 }
1092 };
1093}
1094
1095/// Convenience macro to define a static public key.
1096///
1097/// Input: a single literal base58 string representation of a Pubkey.
1098///
1099/// # Example
1100///
1101/// ```
1102/// use std::str::FromStr;
1103/// use solana_pubkey::{pubkey, Pubkey};
1104///
1105/// static ID: Pubkey = pubkey!("My11111111111111111111111111111111111111111");
1106///
1107/// let my_id = Pubkey::from_str("My11111111111111111111111111111111111111111").unwrap();
1108/// assert_eq!(ID, my_id);
1109/// ```
1110#[macro_export]
1111macro_rules! pubkey {
1112 ($input:literal) => {
1113 $crate::Pubkey::from_str_const($input)
1114 };
1115}
1116
1117/// New random Pubkey for tests and benchmarks.
1118#[cfg(all(feature = "rand", not(target_os = "solana")))]
1119pub fn new_rand() -> Pubkey {
1120 Pubkey::from(rand::random::<[u8; PUBKEY_BYTES]>())
1121}
1122
1123#[cfg(test)]
1124mod tests {
1125 use {super::*, strum::IntoEnumIterator};
1126
1127 #[test]
1128 fn test_new_unique() {
1129 assert!(Pubkey::new_unique() != Pubkey::new_unique());
1130 }
1131
1132 #[test]
1133 fn pubkey_fromstr() {
1134 let pubkey = Pubkey::new_unique();
1135 let mut pubkey_base58_str = bs58::encode(pubkey.0).into_string();
1136
1137 assert_eq!(pubkey_base58_str.parse::<Pubkey>(), Ok(pubkey));
1138
1139 pubkey_base58_str.push_str(&bs58::encode(pubkey.0).into_string());
1140 assert_eq!(
1141 pubkey_base58_str.parse::<Pubkey>(),
1142 Err(ParsePubkeyError::WrongSize)
1143 );
1144
1145 pubkey_base58_str.truncate(pubkey_base58_str.len() / 2);
1146 assert_eq!(pubkey_base58_str.parse::<Pubkey>(), Ok(pubkey));
1147
1148 pubkey_base58_str.truncate(pubkey_base58_str.len() / 2);
1149 assert_eq!(
1150 pubkey_base58_str.parse::<Pubkey>(),
1151 Err(ParsePubkeyError::WrongSize)
1152 );
1153
1154 let mut pubkey_base58_str = bs58::encode(pubkey.0).into_string();
1155 assert_eq!(pubkey_base58_str.parse::<Pubkey>(), Ok(pubkey));
1156
1157 // throw some non-base58 stuff in there
1158 pubkey_base58_str.replace_range(..1, "I");
1159 assert_eq!(
1160 pubkey_base58_str.parse::<Pubkey>(),
1161 Err(ParsePubkeyError::Invalid)
1162 );
1163
1164 // too long input string
1165 // longest valid encoding
1166 let mut too_long = bs58::encode(&[255u8; PUBKEY_BYTES]).into_string();
1167 // and one to grow on
1168 too_long.push('1');
1169 assert_eq!(too_long.parse::<Pubkey>(), Err(ParsePubkeyError::WrongSize));
1170 }
1171
1172 #[test]
1173 fn test_create_with_seed() {
1174 assert!(
1175 Pubkey::create_with_seed(&Pubkey::new_unique(), "☉", &Pubkey::new_unique()).is_ok()
1176 );
1177 assert_eq!(
1178 Pubkey::create_with_seed(
1179 &Pubkey::new_unique(),
1180 from_utf8(&[127; MAX_SEED_LEN + 1]).unwrap(),
1181 &Pubkey::new_unique()
1182 ),
1183 Err(PubkeyError::MaxSeedLengthExceeded)
1184 );
1185 assert!(Pubkey::create_with_seed(
1186 &Pubkey::new_unique(),
1187 "\
1188 \u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\
1189 ",
1190 &Pubkey::new_unique()
1191 )
1192 .is_ok());
1193 // utf-8 abuse ;)
1194 assert_eq!(
1195 Pubkey::create_with_seed(
1196 &Pubkey::new_unique(),
1197 "\
1198 x\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\
1199 ",
1200 &Pubkey::new_unique()
1201 ),
1202 Err(PubkeyError::MaxSeedLengthExceeded)
1203 );
1204
1205 assert!(Pubkey::create_with_seed(
1206 &Pubkey::new_unique(),
1207 from_utf8(&[0; MAX_SEED_LEN]).unwrap(),
1208 &Pubkey::new_unique(),
1209 )
1210 .is_ok());
1211
1212 assert!(
1213 Pubkey::create_with_seed(&Pubkey::new_unique(), "", &Pubkey::new_unique(),).is_ok()
1214 );
1215
1216 assert_eq!(
1217 Pubkey::create_with_seed(
1218 &Pubkey::default(),
1219 "limber chicken: 4/45",
1220 &Pubkey::default(),
1221 ),
1222 Ok("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq"
1223 .parse()
1224 .unwrap())
1225 );
1226 }
1227
1228 #[test]
1229 fn test_create_program_address() {
1230 let exceeded_seed = &[127; MAX_SEED_LEN + 1];
1231 let max_seed = &[0; MAX_SEED_LEN];
1232 let exceeded_seeds: &[&[u8]] = &[
1233 &[1],
1234 &[2],
1235 &[3],
1236 &[4],
1237 &[5],
1238 &[6],
1239 &[7],
1240 &[8],
1241 &[9],
1242 &[10],
1243 &[11],
1244 &[12],
1245 &[13],
1246 &[14],
1247 &[15],
1248 &[16],
1249 &[17],
1250 ];
1251 let max_seeds: &[&[u8]] = &[
1252 &[1],
1253 &[2],
1254 &[3],
1255 &[4],
1256 &[5],
1257 &[6],
1258 &[7],
1259 &[8],
1260 &[9],
1261 &[10],
1262 &[11],
1263 &[12],
1264 &[13],
1265 &[14],
1266 &[15],
1267 &[16],
1268 ];
1269 let program_id = Pubkey::from_str("BPFLoaderUpgradeab1e11111111111111111111111").unwrap();
1270 let public_key = Pubkey::from_str("SeedPubey1111111111111111111111111111111111").unwrap();
1271
1272 assert_eq!(
1273 Pubkey::create_program_address(&[exceeded_seed], &program_id),
1274 Err(PubkeyError::MaxSeedLengthExceeded)
1275 );
1276 assert_eq!(
1277 Pubkey::create_program_address(&[b"short_seed", exceeded_seed], &program_id),
1278 Err(PubkeyError::MaxSeedLengthExceeded)
1279 );
1280 assert!(Pubkey::create_program_address(&[max_seed], &program_id).is_ok());
1281 assert_eq!(
1282 Pubkey::create_program_address(exceeded_seeds, &program_id),
1283 Err(PubkeyError::MaxSeedLengthExceeded)
1284 );
1285 assert!(Pubkey::create_program_address(max_seeds, &program_id).is_ok());
1286 assert_eq!(
1287 Pubkey::create_program_address(&[b"", &[1]], &program_id),
1288 Ok("BwqrghZA2htAcqq8dzP1WDAhTXYTYWj7CHxF5j7TDBAe"
1289 .parse()
1290 .unwrap())
1291 );
1292 assert_eq!(
1293 Pubkey::create_program_address(&["☉".as_ref(), &[0]], &program_id),
1294 Ok("13yWmRpaTR4r5nAktwLqMpRNr28tnVUZw26rTvPSSB19"
1295 .parse()
1296 .unwrap())
1297 );
1298 assert_eq!(
1299 Pubkey::create_program_address(&[b"Talking", b"Squirrels"], &program_id),
1300 Ok("2fnQrngrQT4SeLcdToJAD96phoEjNL2man2kfRLCASVk"
1301 .parse()
1302 .unwrap())
1303 );
1304 assert_eq!(
1305 Pubkey::create_program_address(&[public_key.as_ref(), &[1]], &program_id),
1306 Ok("976ymqVnfE32QFe6NfGDctSvVa36LWnvYxhU6G2232YL"
1307 .parse()
1308 .unwrap())
1309 );
1310 assert_ne!(
1311 Pubkey::create_program_address(&[b"Talking", b"Squirrels"], &program_id).unwrap(),
1312 Pubkey::create_program_address(&[b"Talking"], &program_id).unwrap(),
1313 );
1314 }
1315
1316 #[test]
1317 fn test_pubkey_off_curve() {
1318 // try a bunch of random input, all successful generated program
1319 // addresses must land off the curve and be unique
1320 let mut addresses = std::vec![];
1321 for _ in 0..1_000 {
1322 let program_id = Pubkey::new_unique();
1323 let bytes1 = rand::random::<[u8; 10]>();
1324 let bytes2 = rand::random::<[u8; 32]>();
1325 if let Ok(program_address) =
1326 Pubkey::create_program_address(&[&bytes1, &bytes2], &program_id)
1327 {
1328 assert!(!program_address.is_on_curve());
1329 assert!(!addresses.contains(&program_address));
1330 addresses.push(program_address);
1331 }
1332 }
1333 }
1334
1335 #[test]
1336 fn test_find_program_address() {
1337 for _ in 0..1_000 {
1338 let program_id = Pubkey::new_unique();
1339 let (address, bump_seed) =
1340 Pubkey::find_program_address(&[b"Lil'", b"Bits"], &program_id);
1341 assert_eq!(
1342 address,
1343 Pubkey::create_program_address(&[b"Lil'", b"Bits", &[bump_seed]], &program_id)
1344 .unwrap()
1345 );
1346 }
1347 }
1348
1349 fn pubkey_from_seed_by_marker(marker: &[u8]) -> Result<Pubkey, PubkeyError> {
1350 let key = Pubkey::new_unique();
1351 let owner = Pubkey::default();
1352
1353 let mut to_fake = owner.to_bytes().to_vec();
1354 to_fake.extend_from_slice(marker);
1355
1356 let seed = from_utf8(&to_fake[..to_fake.len() - 32]).expect("not utf8");
1357 let base = &Pubkey::try_from(&to_fake[to_fake.len() - 32..]).unwrap();
1358
1359 Pubkey::create_with_seed(&key, seed, base)
1360 }
1361
1362 #[test]
1363 fn test_create_with_seed_rejects_illegal_owner() {
1364 assert_eq!(
1365 pubkey_from_seed_by_marker(PDA_MARKER),
1366 Err(PubkeyError::IllegalOwner)
1367 );
1368 assert!(pubkey_from_seed_by_marker(&PDA_MARKER[1..]).is_ok());
1369 }
1370
1371 #[test]
1372 fn test_pubkey_error_from_primitive_exhaustive() {
1373 for variant in PubkeyError::iter() {
1374 let variant_i64 = variant.clone() as i64;
1375 assert_eq!(
1376 PubkeyError::from_repr(variant_i64 as usize),
1377 PubkeyError::from_i64(variant_i64)
1378 );
1379 assert_eq!(PubkeyError::from(variant_i64 as u64), variant);
1380 }
1381 }
1382
1383 #[test]
1384 fn test_parse_pubkey_error_from_primitive_exhaustive() {
1385 for variant in ParsePubkeyError::iter() {
1386 let variant_i64 = variant as i64;
1387 assert_eq!(
1388 ParsePubkeyError::from_repr(variant_i64 as usize),
1389 ParsePubkeyError::from_i64(variant_i64)
1390 );
1391 }
1392 }
1393
1394 #[test]
1395 fn test_pubkey_macro() {
1396 const PK: Pubkey = Pubkey::from_str_const("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq");
1397 assert_eq!(pubkey!("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq"), PK);
1398 assert_eq!(
1399 Pubkey::from_str("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq").unwrap(),
1400 PK
1401 );
1402 }
1403}