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