miraland_program/pubkey.rs
1//! Miraland account addresses.
2
3#![allow(clippy::arithmetic_side_effects)]
4
5#[cfg(test)]
6use arbitrary::Arbitrary;
7use {
8 crate::{decode_error::DecodeError, hash::hashv, wasm_bindgen},
9 borsh::{BorshDeserialize, BorshSchema, BorshSerialize},
10 bytemuck::{Pod, Zeroable},
11 num_derive::{FromPrimitive, ToPrimitive},
12 std::{
13 convert::{Infallible, TryFrom},
14 fmt, mem,
15 str::FromStr,
16 },
17 thiserror::Error,
18};
19
20/// Number of bytes in a pubkey
21pub const PUBKEY_BYTES: usize = 32;
22/// maximum length of derived `Pubkey` seed
23pub const MAX_SEED_LEN: usize = 32;
24/// Maximum number of seeds
25pub const MAX_SEEDS: usize = 16;
26/// Maximum string length of a base58 encoded pubkey
27const MAX_BASE58_LEN: usize = 44;
28
29const PDA_MARKER: &[u8; 21] = b"ProgramDerivedAddress";
30
31#[derive(Error, Debug, Serialize, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
32pub enum PubkeyError {
33 /// Length of the seed is too long for address generation
34 #[error("Length of the seed is too long for address generation")]
35 MaxSeedLengthExceeded,
36 #[error("Provided seeds do not result in a valid address")]
37 InvalidSeeds,
38 #[error("Provided owner is not allowed")]
39 IllegalOwner,
40}
41impl<T> DecodeError<T> for PubkeyError {
42 fn type_of() -> &'static str {
43 "PubkeyError"
44 }
45}
46impl From<u64> for PubkeyError {
47 fn from(error: u64) -> Self {
48 match error {
49 0 => PubkeyError::MaxSeedLengthExceeded,
50 1 => PubkeyError::InvalidSeeds,
51 _ => panic!("Unsupported PubkeyError"),
52 }
53 }
54}
55
56/// The address of a [Miraland account][acc].
57///
58/// Some account addresses are [ed25519] public keys, with corresponding secret
59/// keys that are managed off-chain. Often, though, account addresses do not
60/// have corresponding secret keys — as with [_program derived
61/// addresses_][pdas] — or the secret key is not relevant to the operation
62/// of a program, and may have even been disposed of. As running Miraland programs
63/// can not safely create or manage secret keys, the full [`Keypair`] is not
64/// defined in `miraland-program` but in `miraland-sdk`.
65///
66/// [acc]: https://docs.solana.com/developing/programming-model/accounts
67/// [ed25519]: https://ed25519.cr.yp.to/
68/// [pdas]: https://docs.solana.com/developing/programming-model/calling-between-programs#program-derived-addresses
69/// [`Keypair`]: https://docs.rs/solana-sdk/latest/miraland_sdk/signer/keypair/struct.Keypair.html
70#[wasm_bindgen]
71#[repr(transparent)]
72#[derive(
73 AbiExample,
74 BorshDeserialize,
75 BorshSchema,
76 BorshSerialize,
77 Clone,
78 Copy,
79 Default,
80 Deserialize,
81 Eq,
82 Hash,
83 Ord,
84 PartialEq,
85 PartialOrd,
86 Pod,
87 Serialize,
88 Zeroable,
89)]
90#[borsh(crate = "borsh")]
91#[cfg_attr(test, derive(Arbitrary))]
92pub struct Pubkey(pub(crate) [u8; 32]);
93
94impl crate::sanitize::Sanitize for Pubkey {}
95
96#[derive(Error, Debug, Serialize, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
97pub enum ParsePubkeyError {
98 #[error("String is the wrong size")]
99 WrongSize,
100 #[error("Invalid Base58 string")]
101 Invalid,
102}
103
104impl From<Infallible> for ParsePubkeyError {
105 fn from(_: Infallible) -> Self {
106 unreachable!("Infallible uninhabited");
107 }
108}
109
110impl<T> DecodeError<T> for ParsePubkeyError {
111 fn type_of() -> &'static str {
112 "ParsePubkeyError"
113 }
114}
115
116impl FromStr for Pubkey {
117 type Err = ParsePubkeyError;
118
119 fn from_str(s: &str) -> Result<Self, Self::Err> {
120 if s.len() > MAX_BASE58_LEN {
121 return Err(ParsePubkeyError::WrongSize);
122 }
123 let pubkey_vec = bs58::decode(s)
124 .into_vec()
125 .map_err(|_| ParsePubkeyError::Invalid)?;
126 if pubkey_vec.len() != mem::size_of::<Pubkey>() {
127 Err(ParsePubkeyError::WrongSize)
128 } else {
129 Pubkey::try_from(pubkey_vec).map_err(|_| ParsePubkeyError::Invalid)
130 }
131 }
132}
133
134impl From<[u8; 32]> for Pubkey {
135 #[inline]
136 fn from(from: [u8; 32]) -> Self {
137 Self(from)
138 }
139}
140
141impl TryFrom<&[u8]> for Pubkey {
142 type Error = std::array::TryFromSliceError;
143
144 #[inline]
145 fn try_from(pubkey: &[u8]) -> Result<Self, Self::Error> {
146 <[u8; 32]>::try_from(pubkey).map(Self::from)
147 }
148}
149
150impl TryFrom<Vec<u8>> for Pubkey {
151 type Error = Vec<u8>;
152
153 #[inline]
154 fn try_from(pubkey: Vec<u8>) -> Result<Self, Self::Error> {
155 <[u8; 32]>::try_from(pubkey).map(Self::from)
156 }
157}
158
159impl TryFrom<&str> for Pubkey {
160 type Error = ParsePubkeyError;
161 fn try_from(s: &str) -> Result<Self, Self::Error> {
162 Pubkey::from_str(s)
163 }
164}
165
166#[allow(clippy::used_underscore_binding)]
167pub fn bytes_are_curve_point<T: AsRef<[u8]>>(_bytes: T) -> bool {
168 #[cfg(not(target_os = "solana"))]
169 {
170 curve25519_dalek::edwards::CompressedEdwardsY::from_slice(_bytes.as_ref())
171 .decompress()
172 .is_some()
173 }
174 #[cfg(target_os = "solana")]
175 unimplemented!();
176}
177
178impl Pubkey {
179 #[deprecated(
180 since = "1.14.14",
181 note = "Please use 'Pubkey::from' or 'Pubkey::try_from' instead"
182 )]
183 pub fn new(pubkey_vec: &[u8]) -> Self {
184 Self::try_from(pubkey_vec).expect("Slice must be the same length as a Pubkey")
185 }
186
187 pub const fn new_from_array(pubkey_array: [u8; 32]) -> Self {
188 Self(pubkey_array)
189 }
190
191 #[deprecated(since = "1.3.9", note = "Please use 'Pubkey::new_unique' instead")]
192 #[cfg(not(target_os = "solana"))]
193 pub fn new_rand() -> Self {
194 // Consider removing Pubkey::new_rand() entirely in the v1.5 or v1.6 timeframe
195 Pubkey::from(rand::random::<[u8; 32]>())
196 }
197
198 /// unique Pubkey for tests and benchmarks.
199 pub fn new_unique() -> Self {
200 use crate::atomic_u64::AtomicU64;
201 static I: AtomicU64 = AtomicU64::new(1);
202
203 let mut b = [0u8; 32];
204 let i = I.fetch_add(1);
205 // use big endian representation to ensure that recent unique pubkeys
206 // are always greater than less recent unique pubkeys
207 b[0..8].copy_from_slice(&i.to_be_bytes());
208 Self::from(b)
209 }
210
211 pub fn create_with_seed(
212 base: &Pubkey,
213 seed: &str,
214 owner: &Pubkey,
215 ) -> Result<Pubkey, PubkeyError> {
216 if seed.len() > MAX_SEED_LEN {
217 return Err(PubkeyError::MaxSeedLengthExceeded);
218 }
219
220 let owner = owner.as_ref();
221 if owner.len() >= PDA_MARKER.len() {
222 let slice = &owner[owner.len() - PDA_MARKER.len()..];
223 if slice == PDA_MARKER {
224 return Err(PubkeyError::IllegalOwner);
225 }
226 }
227 let hash = hashv(&[base.as_ref(), seed.as_ref(), owner]);
228 Ok(Pubkey::from(hash.to_bytes()))
229 }
230
231 /// Find a valid [program derived address][pda] and its corresponding bump seed.
232 ///
233 /// [pda]: https://docs.solana.com/developing/programming-model/calling-between-programs#program-derived-addresses
234 ///
235 /// Program derived addresses (PDAs) are account keys that only the program,
236 /// `program_id`, has the authority to sign. The address is of the same form
237 /// as a Miraland `Pubkey`, except they are ensured to not be on the ed25519
238 /// curve and thus have no associated private key. When performing
239 /// cross-program invocations the program can "sign" for the key by calling
240 /// [`invoke_signed`] and passing the same seeds used to generate the
241 /// address, along with the calculated _bump seed_, which this function
242 /// returns as the second tuple element. The runtime will verify that the
243 /// program associated with this address is the caller and thus authorized
244 /// to be the signer.
245 ///
246 /// [`invoke_signed`]: crate::program::invoke_signed
247 ///
248 /// The `seeds` are application-specific, and must be carefully selected to
249 /// uniquely derive accounts per application requirements. It is common to
250 /// use static strings and other pubkeys as seeds.
251 ///
252 /// Because the program address must not lie on the ed25519 curve, there may
253 /// be seed and program id combinations that are invalid. For this reason,
254 /// an extra seed (the bump seed) is calculated that results in a
255 /// point off the curve. The bump seed must be passed as an additional seed
256 /// when calling `invoke_signed`.
257 ///
258 /// The processes of finding a valid program address is by trial and error,
259 /// and even though it is deterministic given a set of inputs it can take a
260 /// variable amount of time to succeed across different inputs. This means
261 /// that when called from an on-chain program it may incur a variable amount
262 /// of the program's compute budget. Programs that are meant to be very
263 /// performant may not want to use this function because it could take a
264 /// considerable amount of time. Programs that are already at risk
265 /// of exceeding their compute budget should call this with care since
266 /// there is a chance that the program's budget may be occasionally
267 /// and unpredictably exceeded.
268 ///
269 /// As all account addresses accessed by an on-chain Miraland program must be
270 /// explicitly passed to the program, it is typical for the PDAs to be
271 /// derived in off-chain client programs, avoiding the compute cost of
272 /// generating the address on-chain. The address may or may not then be
273 /// verified by re-deriving it on-chain, depending on the requirements of
274 /// the program. This verification may be performed without the overhead of
275 /// re-searching for the bump key by using the [`create_program_address`]
276 /// function.
277 ///
278 /// [`create_program_address`]: Pubkey::create_program_address
279 ///
280 /// **Warning**: Because of the way the seeds are hashed there is a potential
281 /// for program address collisions for the same program id. The seeds are
282 /// hashed sequentially which means that seeds {"abcdef"}, {"abc", "def"},
283 /// and {"ab", "cd", "ef"} will all result in the same program address given
284 /// the same program id. Since the chance of collision is local to a given
285 /// program id, the developer of that program must take care to choose seeds
286 /// that do not collide with each other. For seed schemes that are susceptible
287 /// to this type of hash collision, a common remedy is to insert separators
288 /// between seeds, e.g. transforming {"abc", "def"} into {"abc", "-", "def"}.
289 ///
290 /// # Panics
291 ///
292 /// Panics in the statistically improbable event that a bump seed could not be
293 /// found. Use [`try_find_program_address`] to handle this case.
294 ///
295 /// [`try_find_program_address`]: Pubkey::try_find_program_address
296 ///
297 /// Panics if any of the following are true:
298 ///
299 /// - the number of provided seeds is greater than, _or equal to_, [`MAX_SEEDS`],
300 /// - any individual seed's length is greater than [`MAX_SEED_LEN`].
301 ///
302 /// # Examples
303 ///
304 /// This example illustrates a simple case of creating a "vault" account
305 /// which is derived from the payer account, but owned by an on-chain
306 /// program. The program derived address is derived in an off-chain client
307 /// program, which invokes an on-chain Miraland program that uses the address
308 /// to create a new account owned and controlled by the program itself.
309 ///
310 /// By convention, the on-chain program will be compiled for use in two
311 /// different contexts: both on-chain, to interpret a custom program
312 /// instruction as a Miraland transaction; and off-chain, as a library, so
313 /// that clients can share the instruction data structure, constructors, and
314 /// other common code.
315 ///
316 /// First the on-chain Miraland program:
317 ///
318 /// ```
319 /// # use borsh::{BorshSerialize, BorshDeserialize};
320 /// # use miraland_program::{
321 /// # pubkey::Pubkey,
322 /// # entrypoint::ProgramResult,
323 /// # program::invoke_signed,
324 /// # system_instruction,
325 /// # account_info::{
326 /// # AccountInfo,
327 /// # next_account_info,
328 /// # },
329 /// # };
330 /// // The custom instruction processed by our program. It includes the
331 /// // PDA's bump seed, which is derived by the client program. This
332 /// // definition is also imported into the off-chain client program.
333 /// // The computed address of the PDA will be passed to this program via
334 /// // the `accounts` vector of the `Instruction` type.
335 /// #[derive(BorshSerialize, BorshDeserialize, Debug)]
336 /// # #[borsh(crate = "borsh")]
337 /// pub struct InstructionData {
338 /// pub vault_bump_seed: u8,
339 /// pub lamports: u64,
340 /// }
341 ///
342 /// // The size in bytes of a vault account. The client program needs
343 /// // this information to calculate the quantity of lamports necessary
344 /// // to pay for the account's rent.
345 /// pub static VAULT_ACCOUNT_SIZE: u64 = 1024;
346 ///
347 /// // The entrypoint of the on-chain program, as provided to the
348 /// // `entrypoint!` macro.
349 /// fn process_instruction(
350 /// program_id: &Pubkey,
351 /// accounts: &[AccountInfo],
352 /// instruction_data: &[u8],
353 /// ) -> ProgramResult {
354 /// let account_info_iter = &mut accounts.iter();
355 /// let payer = next_account_info(account_info_iter)?;
356 /// // The vault PDA, derived from the payer's address
357 /// let vault = next_account_info(account_info_iter)?;
358 ///
359 /// let mut instruction_data = instruction_data;
360 /// let instr = InstructionData::deserialize(&mut instruction_data)?;
361 /// let vault_bump_seed = instr.vault_bump_seed;
362 /// let lamports = instr.lamports;
363 /// let vault_size = VAULT_ACCOUNT_SIZE;
364 ///
365 /// // Invoke the system program to create an account while virtually
366 /// // signing with the vault PDA, which is owned by this caller program.
367 /// invoke_signed(
368 /// &system_instruction::create_account(
369 /// &payer.key,
370 /// &vault.key,
371 /// lamports,
372 /// vault_size,
373 /// &program_id,
374 /// ),
375 /// &[
376 /// payer.clone(),
377 /// vault.clone(),
378 /// ],
379 /// // A slice of seed slices, each seed slice being the set
380 /// // of seeds used to generate one of the PDAs required by the
381 /// // callee program, the final seed being a single-element slice
382 /// // containing the `u8` bump seed.
383 /// &[
384 /// &[
385 /// b"vault",
386 /// payer.key.as_ref(),
387 /// &[vault_bump_seed],
388 /// ],
389 /// ]
390 /// )?;
391 ///
392 /// Ok(())
393 /// }
394 /// ```
395 ///
396 /// The client program:
397 ///
398 /// ```
399 /// # use borsh::{BorshSerialize, BorshDeserialize};
400 /// # use miraland_program::example_mocks::{miraland_sdk, miraland_rpc_client};
401 /// # use miraland_program::{
402 /// # pubkey::Pubkey,
403 /// # instruction::Instruction,
404 /// # hash::Hash,
405 /// # instruction::AccountMeta,
406 /// # system_program,
407 /// # };
408 /// # use miraland_sdk::{
409 /// # signature::Keypair,
410 /// # signature::{Signer, Signature},
411 /// # transaction::Transaction,
412 /// # };
413 /// # use miraland_rpc_client::rpc_client::RpcClient;
414 /// # use std::convert::TryFrom;
415 /// # use anyhow::Result;
416 /// #
417 /// # #[derive(BorshSerialize, BorshDeserialize, Debug)]
418 /// # #[borsh(crate = "borsh")]
419 /// # struct InstructionData {
420 /// # pub vault_bump_seed: u8,
421 /// # pub lamports: u64,
422 /// # }
423 /// #
424 /// # pub static VAULT_ACCOUNT_SIZE: u64 = 1024;
425 /// #
426 /// fn create_vault_account(
427 /// client: &RpcClient,
428 /// program_id: Pubkey,
429 /// payer: &Keypair,
430 /// ) -> Result<()> {
431 /// // Derive the PDA from the payer account, a string representing the unique
432 /// // purpose of the account ("vault"), and the address of our on-chain program.
433 /// let (vault_pubkey, vault_bump_seed) = Pubkey::find_program_address(
434 /// &[b"vault", payer.pubkey().as_ref()],
435 /// &program_id
436 /// );
437 ///
438 /// // Get the amount of lamports needed to pay for the vault's rent
439 /// let vault_account_size = usize::try_from(VAULT_ACCOUNT_SIZE)?;
440 /// let lamports = client.get_minimum_balance_for_rent_exemption(vault_account_size)?;
441 ///
442 /// // The on-chain program's instruction data, imported from that program's crate.
443 /// let instr_data = InstructionData {
444 /// vault_bump_seed,
445 /// lamports,
446 /// };
447 ///
448 /// // The accounts required by both our on-chain program and the system program's
449 /// // `create_account` instruction, including the vault's address.
450 /// let accounts = vec![
451 /// AccountMeta::new(payer.pubkey(), true),
452 /// AccountMeta::new(vault_pubkey, false),
453 /// AccountMeta::new(system_program::ID, false),
454 /// ];
455 ///
456 /// // Create the instruction by serializing our instruction data via borsh
457 /// let instruction = Instruction::new_with_borsh(
458 /// program_id,
459 /// &instr_data,
460 /// accounts,
461 /// );
462 ///
463 /// let blockhash = client.get_latest_blockhash()?;
464 ///
465 /// let transaction = Transaction::new_signed_with_payer(
466 /// &[instruction],
467 /// Some(&payer.pubkey()),
468 /// &[payer],
469 /// blockhash,
470 /// );
471 ///
472 /// client.send_and_confirm_transaction(&transaction)?;
473 ///
474 /// Ok(())
475 /// }
476 /// # let program_id = Pubkey::new_unique();
477 /// # let payer = Keypair::new();
478 /// # let client = RpcClient::new(String::new());
479 /// #
480 /// # create_vault_account(&client, program_id, &payer)?;
481 /// #
482 /// # Ok::<(), anyhow::Error>(())
483 /// ```
484 pub fn find_program_address(seeds: &[&[u8]], program_id: &Pubkey) -> (Pubkey, u8) {
485 Self::try_find_program_address(seeds, program_id)
486 .unwrap_or_else(|| panic!("Unable to find a viable program address bump seed"))
487 }
488
489 /// Find a valid [program derived address][pda] and its corresponding bump seed.
490 ///
491 /// [pda]: https://docs.solana.com/developing/programming-model/calling-between-programs#program-derived-addresses
492 ///
493 /// The only difference between this method and [`find_program_address`]
494 /// is that this one returns `None` in the statistically improbable event
495 /// that a bump seed cannot be found; or if any of `find_program_address`'s
496 /// preconditions are violated.
497 ///
498 /// See the documentation for [`find_program_address`] for a full description.
499 ///
500 /// [`find_program_address`]: Pubkey::find_program_address
501 #[allow(clippy::same_item_push)]
502 pub fn try_find_program_address(seeds: &[&[u8]], program_id: &Pubkey) -> Option<(Pubkey, u8)> {
503 // Perform the calculation inline, calling this from within a program is
504 // not supported
505 #[cfg(not(target_os = "solana"))]
506 {
507 let mut bump_seed = [std::u8::MAX];
508 for _ in 0..std::u8::MAX {
509 {
510 let mut seeds_with_bump = seeds.to_vec();
511 seeds_with_bump.push(&bump_seed);
512 match Self::create_program_address(&seeds_with_bump, program_id) {
513 Ok(address) => return Some((address, bump_seed[0])),
514 Err(PubkeyError::InvalidSeeds) => (),
515 _ => break,
516 }
517 }
518 bump_seed[0] -= 1;
519 }
520 None
521 }
522 // Call via a system call to perform the calculation
523 #[cfg(target_os = "solana")]
524 {
525 let mut bytes = [0; 32];
526 let mut bump_seed = std::u8::MAX;
527 let result = unsafe {
528 crate::syscalls::sol_try_find_program_address(
529 seeds as *const _ as *const u8,
530 seeds.len() as u64,
531 program_id as *const _ as *const u8,
532 &mut bytes as *mut _ as *mut u8,
533 &mut bump_seed as *mut _ as *mut u8,
534 )
535 };
536 match result {
537 crate::entrypoint::SUCCESS => Some((Pubkey::from(bytes), bump_seed)),
538 _ => None,
539 }
540 }
541 }
542
543 /// Create a valid [program derived address][pda] without searching for a bump seed.
544 ///
545 /// [pda]: https://docs.solana.com/developing/programming-model/calling-between-programs#program-derived-addresses
546 ///
547 /// Because this function does not create a bump seed, it may unpredictably
548 /// return an error for any given set of seeds and is not generally suitable
549 /// for creating program derived addresses.
550 ///
551 /// However, it can be used for efficiently verifying that a set of seeds plus
552 /// bump seed generated by [`find_program_address`] derives a particular
553 /// address as expected. See the example for details.
554 ///
555 /// See the documentation for [`find_program_address`] for a full description
556 /// of program derived addresses and bump seeds.
557 ///
558 /// [`find_program_address`]: Pubkey::find_program_address
559 ///
560 /// # Examples
561 ///
562 /// Creating a program derived address involves iteratively searching for a
563 /// bump seed for which the derived [`Pubkey`] does not lie on the ed25519
564 /// curve. This search process is generally performed off-chain, with the
565 /// [`find_program_address`] function, after which the client passes the
566 /// bump seed to the program as instruction data.
567 ///
568 /// Depending on the application requirements, a program may wish to verify
569 /// that the set of seeds, plus the bump seed, do correctly generate an
570 /// expected address.
571 ///
572 /// The verification is performed by appending to the other seeds one
573 /// additional seed slice that contains the single `u8` bump seed, calling
574 /// `create_program_address`, checking that the return value is `Ok`, and
575 /// that the returned `Pubkey` has the expected value.
576 ///
577 /// ```
578 /// # use miraland_program::pubkey::Pubkey;
579 /// # let program_id = Pubkey::new_unique();
580 /// let (expected_pda, bump_seed) = Pubkey::find_program_address(&[b"vault"], &program_id);
581 /// let actual_pda = Pubkey::create_program_address(&[b"vault", &[bump_seed]], &program_id)?;
582 /// assert_eq!(expected_pda, actual_pda);
583 /// # Ok::<(), anyhow::Error>(())
584 /// ```
585 pub fn create_program_address(
586 seeds: &[&[u8]],
587 program_id: &Pubkey,
588 ) -> Result<Pubkey, PubkeyError> {
589 if seeds.len() > MAX_SEEDS {
590 return Err(PubkeyError::MaxSeedLengthExceeded);
591 }
592 for seed in seeds.iter() {
593 if seed.len() > MAX_SEED_LEN {
594 return Err(PubkeyError::MaxSeedLengthExceeded);
595 }
596 }
597
598 // Perform the calculation inline, calling this from within a program is
599 // not supported
600 #[cfg(not(target_os = "solana"))]
601 {
602 let mut hasher = crate::hash::Hasher::default();
603 for seed in seeds.iter() {
604 hasher.hash(seed);
605 }
606 hasher.hashv(&[program_id.as_ref(), PDA_MARKER]);
607 let hash = hasher.result();
608
609 if bytes_are_curve_point(hash) {
610 return Err(PubkeyError::InvalidSeeds);
611 }
612
613 Ok(Pubkey::from(hash.to_bytes()))
614 }
615 // Call via a system call to perform the calculation
616 #[cfg(target_os = "solana")]
617 {
618 let mut bytes = [0; 32];
619 let result = unsafe {
620 crate::syscalls::sol_create_program_address(
621 seeds as *const _ as *const u8,
622 seeds.len() as u64,
623 program_id as *const _ as *const u8,
624 &mut bytes as *mut _ as *mut u8,
625 )
626 };
627 match result {
628 crate::entrypoint::SUCCESS => Ok(Pubkey::from(bytes)),
629 _ => Err(result.into()),
630 }
631 }
632 }
633
634 pub fn to_bytes(self) -> [u8; 32] {
635 self.0
636 }
637
638 pub fn is_on_curve(&self) -> bool {
639 bytes_are_curve_point(self)
640 }
641
642 /// Log a `Pubkey` from a program
643 pub fn log(&self) {
644 #[cfg(target_os = "solana")]
645 unsafe {
646 crate::syscalls::sol_log_pubkey(self.as_ref() as *const _ as *const u8)
647 };
648
649 #[cfg(not(target_os = "solana"))]
650 crate::program_stubs::sol_log(&self.to_string());
651 }
652}
653
654impl AsRef<[u8]> for Pubkey {
655 fn as_ref(&self) -> &[u8] {
656 &self.0[..]
657 }
658}
659
660impl AsMut<[u8]> for Pubkey {
661 fn as_mut(&mut self) -> &mut [u8] {
662 &mut self.0[..]
663 }
664}
665
666impl fmt::Debug for Pubkey {
667 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
668 write!(f, "{}", bs58::encode(self.0).into_string())
669 }
670}
671
672impl fmt::Display for Pubkey {
673 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
674 write!(f, "{}", bs58::encode(self.0).into_string())
675 }
676}
677
678impl borsh0_10::de::BorshDeserialize for Pubkey {
679 fn deserialize_reader<R: borsh0_10::maybestd::io::Read>(
680 reader: &mut R,
681 ) -> ::core::result::Result<Self, borsh0_10::maybestd::io::Error> {
682 Ok(Self(borsh0_10::BorshDeserialize::deserialize_reader(
683 reader,
684 )?))
685 }
686}
687impl borsh0_9::de::BorshDeserialize for Pubkey {
688 fn deserialize(buf: &mut &[u8]) -> ::core::result::Result<Self, borsh0_9::maybestd::io::Error> {
689 Ok(Self(borsh0_9::BorshDeserialize::deserialize(buf)?))
690 }
691}
692
693macro_rules! impl_borsh_schema {
694 ($borsh:ident) => {
695 impl $borsh::BorshSchema for Pubkey
696 where
697 [u8; 32]: $borsh::BorshSchema,
698 {
699 fn declaration() -> $borsh::schema::Declaration {
700 "Pubkey".to_string()
701 }
702 fn add_definitions_recursively(
703 definitions: &mut $borsh::maybestd::collections::HashMap<
704 $borsh::schema::Declaration,
705 $borsh::schema::Definition,
706 >,
707 ) {
708 let fields = $borsh::schema::Fields::UnnamedFields(<[_]>::into_vec(
709 $borsh::maybestd::boxed::Box::new([
710 <[u8; 32] as $borsh::BorshSchema>::declaration(),
711 ]),
712 ));
713 let definition = $borsh::schema::Definition::Struct { fields };
714 <Self as $borsh::BorshSchema>::add_definition(
715 <Self as $borsh::BorshSchema>::declaration(),
716 definition,
717 definitions,
718 );
719 <[u8; 32] as $borsh::BorshSchema>::add_definitions_recursively(definitions);
720 }
721 }
722 };
723}
724impl_borsh_schema!(borsh0_10);
725impl_borsh_schema!(borsh0_9);
726
727macro_rules! impl_borsh_serialize {
728 ($borsh:ident) => {
729 impl $borsh::ser::BorshSerialize for Pubkey {
730 fn serialize<W: $borsh::maybestd::io::Write>(
731 &self,
732 writer: &mut W,
733 ) -> ::core::result::Result<(), $borsh::maybestd::io::Error> {
734 $borsh::BorshSerialize::serialize(&self.0, writer)?;
735 Ok(())
736 }
737 }
738 };
739}
740impl_borsh_serialize!(borsh0_10);
741impl_borsh_serialize!(borsh0_9);
742
743#[cfg(test)]
744mod tests {
745 use {super::*, std::str::from_utf8};
746
747 #[test]
748 fn test_new_unique() {
749 assert!(Pubkey::new_unique() != Pubkey::new_unique());
750 }
751
752 #[test]
753 fn pubkey_fromstr() {
754 let pubkey = Pubkey::new_unique();
755 let mut pubkey_base58_str = bs58::encode(pubkey.0).into_string();
756
757 assert_eq!(pubkey_base58_str.parse::<Pubkey>(), Ok(pubkey));
758
759 pubkey_base58_str.push_str(&bs58::encode(pubkey.0).into_string());
760 assert_eq!(
761 pubkey_base58_str.parse::<Pubkey>(),
762 Err(ParsePubkeyError::WrongSize)
763 );
764
765 pubkey_base58_str.truncate(pubkey_base58_str.len() / 2);
766 assert_eq!(pubkey_base58_str.parse::<Pubkey>(), Ok(pubkey));
767
768 pubkey_base58_str.truncate(pubkey_base58_str.len() / 2);
769 assert_eq!(
770 pubkey_base58_str.parse::<Pubkey>(),
771 Err(ParsePubkeyError::WrongSize)
772 );
773
774 let mut pubkey_base58_str = bs58::encode(pubkey.0).into_string();
775 assert_eq!(pubkey_base58_str.parse::<Pubkey>(), Ok(pubkey));
776
777 // throw some non-base58 stuff in there
778 pubkey_base58_str.replace_range(..1, "I");
779 assert_eq!(
780 pubkey_base58_str.parse::<Pubkey>(),
781 Err(ParsePubkeyError::Invalid)
782 );
783
784 // too long input string
785 // longest valid encoding
786 let mut too_long = bs58::encode(&[255u8; PUBKEY_BYTES]).into_string();
787 // and one to grow on
788 too_long.push('1');
789 assert_eq!(too_long.parse::<Pubkey>(), Err(ParsePubkeyError::WrongSize));
790 }
791
792 #[test]
793 fn test_create_with_seed() {
794 assert!(
795 Pubkey::create_with_seed(&Pubkey::new_unique(), "☉", &Pubkey::new_unique()).is_ok()
796 );
797 assert_eq!(
798 Pubkey::create_with_seed(
799 &Pubkey::new_unique(),
800 from_utf8(&[127; MAX_SEED_LEN + 1]).unwrap(),
801 &Pubkey::new_unique()
802 ),
803 Err(PubkeyError::MaxSeedLengthExceeded)
804 );
805 assert!(Pubkey::create_with_seed(
806 &Pubkey::new_unique(),
807 "\
808 \u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\
809 ",
810 &Pubkey::new_unique()
811 )
812 .is_ok());
813 // utf-8 abuse ;)
814 assert_eq!(
815 Pubkey::create_with_seed(
816 &Pubkey::new_unique(),
817 "\
818 x\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\
819 ",
820 &Pubkey::new_unique()
821 ),
822 Err(PubkeyError::MaxSeedLengthExceeded)
823 );
824
825 assert!(Pubkey::create_with_seed(
826 &Pubkey::new_unique(),
827 std::str::from_utf8(&[0; MAX_SEED_LEN]).unwrap(),
828 &Pubkey::new_unique(),
829 )
830 .is_ok());
831
832 assert!(
833 Pubkey::create_with_seed(&Pubkey::new_unique(), "", &Pubkey::new_unique(),).is_ok()
834 );
835
836 assert_eq!(
837 Pubkey::create_with_seed(
838 &Pubkey::default(),
839 "limber chicken: 4/45",
840 &Pubkey::default(),
841 ),
842 Ok("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq"
843 .parse()
844 .unwrap())
845 );
846 }
847
848 #[test]
849 fn test_create_program_address() {
850 let exceeded_seed = &[127; MAX_SEED_LEN + 1];
851 let max_seed = &[0; MAX_SEED_LEN];
852 let exceeded_seeds: &[&[u8]] = &[
853 &[1],
854 &[2],
855 &[3],
856 &[4],
857 &[5],
858 &[6],
859 &[7],
860 &[8],
861 &[9],
862 &[10],
863 &[11],
864 &[12],
865 &[13],
866 &[14],
867 &[15],
868 &[16],
869 &[17],
870 ];
871 let max_seeds: &[&[u8]] = &[
872 &[1],
873 &[2],
874 &[3],
875 &[4],
876 &[5],
877 &[6],
878 &[7],
879 &[8],
880 &[9],
881 &[10],
882 &[11],
883 &[12],
884 &[13],
885 &[14],
886 &[15],
887 &[16],
888 ];
889 let program_id = Pubkey::from_str("BPFLoaderUpgradeab1e11111111111111111111111").unwrap();
890 let public_key = Pubkey::from_str("SeedPubey1111111111111111111111111111111111").unwrap();
891
892 assert_eq!(
893 Pubkey::create_program_address(&[exceeded_seed], &program_id),
894 Err(PubkeyError::MaxSeedLengthExceeded)
895 );
896 assert_eq!(
897 Pubkey::create_program_address(&[b"short_seed", exceeded_seed], &program_id),
898 Err(PubkeyError::MaxSeedLengthExceeded)
899 );
900 assert!(Pubkey::create_program_address(&[max_seed], &program_id).is_ok());
901 assert_eq!(
902 Pubkey::create_program_address(exceeded_seeds, &program_id),
903 Err(PubkeyError::MaxSeedLengthExceeded)
904 );
905 assert!(Pubkey::create_program_address(max_seeds, &program_id).is_ok());
906 assert_eq!(
907 Pubkey::create_program_address(&[b"", &[1]], &program_id),
908 Ok("BwqrghZA2htAcqq8dzP1WDAhTXYTYWj7CHxF5j7TDBAe"
909 .parse()
910 .unwrap())
911 );
912 assert_eq!(
913 Pubkey::create_program_address(&["☉".as_ref(), &[0]], &program_id),
914 Ok("13yWmRpaTR4r5nAktwLqMpRNr28tnVUZw26rTvPSSB19"
915 .parse()
916 .unwrap())
917 );
918 assert_eq!(
919 Pubkey::create_program_address(&[b"Talking", b"Squirrels"], &program_id),
920 Ok("2fnQrngrQT4SeLcdToJAD96phoEjNL2man2kfRLCASVk"
921 .parse()
922 .unwrap())
923 );
924 assert_eq!(
925 Pubkey::create_program_address(&[public_key.as_ref(), &[1]], &program_id),
926 Ok("976ymqVnfE32QFe6NfGDctSvVa36LWnvYxhU6G2232YL"
927 .parse()
928 .unwrap())
929 );
930 assert_ne!(
931 Pubkey::create_program_address(&[b"Talking", b"Squirrels"], &program_id).unwrap(),
932 Pubkey::create_program_address(&[b"Talking"], &program_id).unwrap(),
933 );
934 }
935
936 #[test]
937 fn test_pubkey_off_curve() {
938 // try a bunch of random input, all successful generated program
939 // addresses must land off the curve and be unique
940 let mut addresses = vec![];
941 for _ in 0..1_000 {
942 let program_id = Pubkey::new_unique();
943 let bytes1 = rand::random::<[u8; 10]>();
944 let bytes2 = rand::random::<[u8; 32]>();
945 if let Ok(program_address) =
946 Pubkey::create_program_address(&[&bytes1, &bytes2], &program_id)
947 {
948 let is_on_curve = curve25519_dalek::edwards::CompressedEdwardsY::from_slice(
949 &program_address.to_bytes(),
950 )
951 .decompress()
952 .is_some();
953 assert!(!is_on_curve);
954 assert!(!addresses.contains(&program_address));
955 addresses.push(program_address);
956 }
957 }
958 }
959
960 #[test]
961 fn test_find_program_address() {
962 for _ in 0..1_000 {
963 let program_id = Pubkey::new_unique();
964 let (address, bump_seed) =
965 Pubkey::find_program_address(&[b"Lil'", b"Bits"], &program_id);
966 assert_eq!(
967 address,
968 Pubkey::create_program_address(&[b"Lil'", b"Bits", &[bump_seed]], &program_id)
969 .unwrap()
970 );
971 }
972 }
973
974 fn pubkey_from_seed_by_marker(marker: &[u8]) -> Result<Pubkey, PubkeyError> {
975 let key = Pubkey::new_unique();
976 let owner = Pubkey::default();
977
978 let mut to_fake = owner.to_bytes().to_vec();
979 to_fake.extend_from_slice(marker);
980
981 let seed = &String::from_utf8(to_fake[..to_fake.len() - 32].to_vec()).expect("not utf8");
982 let base = &Pubkey::try_from_slice(&to_fake[to_fake.len() - 32..]).unwrap();
983
984 Pubkey::create_with_seed(&key, seed, base)
985 }
986
987 #[test]
988 fn test_create_with_seed_rejects_illegal_owner() {
989 assert_eq!(
990 pubkey_from_seed_by_marker(PDA_MARKER),
991 Err(PubkeyError::IllegalOwner)
992 );
993 assert!(pubkey_from_seed_by_marker(&PDA_MARKER[1..]).is_ok());
994 }
995}