1#![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,
27 hash::{Hash, Hasher},
28 mem,
29 str::{from_utf8, FromStr},
30 },
31 num_traits::{FromPrimitive, ToPrimitive},
32 solana_decode_error::DecodeError,
33};
34#[cfg(target_arch = "wasm32")]
35use {
36 js_sys::{Array, Uint8Array},
37 wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue},
38};
39
40#[cfg(target_os = "solana")]
41pub mod syscalls;
42
43pub const PUBKEY_BYTES: usize = 32;
45pub const MAX_SEED_LEN: usize = 32;
47pub const MAX_SEEDS: usize = 16;
49const MAX_BASE58_LEN: usize = 44;
51
52#[cfg(any(target_os = "solana", feature = "sha2", feature = "curve25519"))]
53const PDA_MARKER: &[u8; 21] = b"ProgramDerivedAddress";
54
55#[cfg(target_os = "solana")]
58const SUCCESS: u64 = 0;
59
60#[cfg_attr(test, derive(strum_macros::FromRepr, strum_macros::EnumIter))]
63#[cfg_attr(feature = "serde", derive(Serialize))]
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub enum PubkeyError {
66 MaxSeedLengthExceeded,
68 InvalidSeeds,
69 IllegalOwner,
70}
71
72impl ToPrimitive for PubkeyError {
73 #[inline]
74 fn to_i64(&self) -> Option<i64> {
75 Some(match *self {
76 PubkeyError::MaxSeedLengthExceeded => PubkeyError::MaxSeedLengthExceeded as i64,
77 PubkeyError::InvalidSeeds => PubkeyError::InvalidSeeds as i64,
78 PubkeyError::IllegalOwner => PubkeyError::IllegalOwner as i64,
79 })
80 }
81 #[inline]
82 fn to_u64(&self) -> Option<u64> {
83 self.to_i64().map(|x| x as u64)
84 }
85}
86
87impl FromPrimitive for PubkeyError {
88 #[inline]
89 fn from_i64(n: i64) -> Option<Self> {
90 if n == PubkeyError::MaxSeedLengthExceeded as i64 {
91 Some(PubkeyError::MaxSeedLengthExceeded)
92 } else if n == PubkeyError::InvalidSeeds as i64 {
93 Some(PubkeyError::InvalidSeeds)
94 } else if n == PubkeyError::IllegalOwner as i64 {
95 Some(PubkeyError::IllegalOwner)
96 } else {
97 None
98 }
99 }
100 #[inline]
101 fn from_u64(n: u64) -> Option<Self> {
102 Self::from_i64(n as i64)
103 }
104}
105
106#[cfg(feature = "std")]
107impl std::error::Error for PubkeyError {}
108
109impl fmt::Display for PubkeyError {
110 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
111 match self {
112 PubkeyError::MaxSeedLengthExceeded => {
113 f.write_str("Length of the seed is too long for address generation")
114 }
115 PubkeyError::InvalidSeeds => {
116 f.write_str("Provided seeds do not result in a valid address")
117 }
118 PubkeyError::IllegalOwner => f.write_str("Provided owner is not allowed"),
119 }
120 }
121}
122
123impl<T> DecodeError<T> for PubkeyError {
124 fn type_of() -> &'static str {
125 "PubkeyError"
126 }
127}
128impl From<u64> for PubkeyError {
129 fn from(error: u64) -> Self {
130 match error {
131 0 => PubkeyError::MaxSeedLengthExceeded,
132 1 => PubkeyError::InvalidSeeds,
133 2 => PubkeyError::IllegalOwner,
134 _ => panic!("Unsupported PubkeyError"),
135 }
136 }
137}
138
139#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
154#[repr(transparent)]
155#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))]
156#[cfg_attr(
157 feature = "borsh",
158 derive(BorshSerialize, BorshDeserialize),
159 borsh(crate = "borsh")
160)]
161#[cfg_attr(all(feature = "borsh", feature = "std"), derive(BorshSchema))]
162#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
163#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
164#[derive(Clone, Copy, Default, Eq, Ord, PartialEq, PartialOrd)]
165#[cfg_attr(feature = "dev-context-only-utils", derive(Arbitrary))]
166pub struct Pubkey(pub(crate) [u8; 32]);
167
168impl Hash for Pubkey {
172 fn hash<H: Hasher>(&self, state: &mut H) {
173 state.write(self.as_array());
174 }
175}
176
177#[cfg(all(feature = "rand", not(target_os = "solana")))]
178mod hasher {
179 use {
180 crate::PUBKEY_BYTES,
181 core::{
182 cell::Cell,
183 hash::{BuildHasher, Hasher},
184 mem,
185 },
186 rand::{thread_rng, Rng},
187 };
188
189 #[derive(Default)]
197 pub struct PubkeyHasher {
198 offset: usize,
199 state: u64,
200 }
201
202 impl Hasher for PubkeyHasher {
203 #[inline]
204 fn finish(&self) -> u64 {
205 self.state
206 }
207 #[inline]
208 fn write(&mut self, bytes: &[u8]) {
209 debug_assert_eq!(
210 bytes.len(),
211 PUBKEY_BYTES,
212 "This hasher is intended to be used with pubkeys and nothing else"
213 );
214 let chunk: &[u8; mem::size_of::<u64>()] = bytes
216 [self.offset..self.offset + mem::size_of::<u64>()]
217 .try_into()
218 .unwrap();
219 self.state = u64::from_ne_bytes(*chunk);
220 }
221 }
222
223 #[derive(Clone)]
231 pub struct PubkeyHasherBuilder {
232 offset: usize,
233 }
234
235 impl Default for PubkeyHasherBuilder {
236 fn default() -> Self {
244 std::thread_local!(static OFFSET: Cell<usize> = {
245 let mut rng = thread_rng();
246 Cell::new(rng.gen_range(0..PUBKEY_BYTES - mem::size_of::<u64>()))
247 });
248
249 let offset = OFFSET.with(|offset| {
250 let mut next_offset = offset.get() + 1;
251 if next_offset > PUBKEY_BYTES - mem::size_of::<u64>() {
252 next_offset = 0;
253 }
254 offset.set(next_offset);
255 next_offset
256 });
257 PubkeyHasherBuilder { offset }
258 }
259 }
260
261 impl BuildHasher for PubkeyHasherBuilder {
262 type Hasher = PubkeyHasher;
263 #[inline]
264 fn build_hasher(&self) -> Self::Hasher {
265 PubkeyHasher {
266 offset: self.offset,
267 state: 0,
268 }
269 }
270 }
271
272 #[cfg(test)]
273 mod tests {
274 use {
275 super::PubkeyHasherBuilder,
276 crate::Pubkey,
277 core::hash::{BuildHasher, Hasher},
278 };
279 #[test]
280 fn test_pubkey_hasher_builder() {
281 let key = Pubkey::new_unique();
282 let builder = PubkeyHasherBuilder::default();
283 let mut hasher1 = builder.build_hasher();
284 let mut hasher2 = builder.build_hasher();
285 hasher1.write(key.as_array());
286 hasher2.write(key.as_array());
287 assert_eq!(
288 hasher1.finish(),
289 hasher2.finish(),
290 "Hashers made with same builder should be identical"
291 );
292 let builder2 = PubkeyHasherBuilder::default();
295 for _ in 0..64 {
296 let mut hasher3 = builder2.build_hasher();
297 hasher3.write(key.as_array());
298 std::dbg!(hasher1.finish());
299 std::dbg!(hasher3.finish());
300 if hasher1.finish() != hasher3.finish() {
301 return;
302 }
303 }
304 panic!("Hashers built with different builder should be different due to random offset");
305 }
306
307 #[test]
308 fn test_pubkey_hasher() {
309 let key1 = Pubkey::new_unique();
310 let key2 = Pubkey::new_unique();
311 let builder = PubkeyHasherBuilder::default();
312 let mut hasher1 = builder.build_hasher();
313 let mut hasher2 = builder.build_hasher();
314 hasher1.write(key1.as_array());
315 hasher2.write(key2.as_array());
316 assert_ne!(hasher1.finish(), hasher2.finish());
317 }
318 }
319}
320#[cfg(all(feature = "rand", not(target_os = "solana")))]
321pub use hasher::{PubkeyHasher, PubkeyHasherBuilder};
322
323impl solana_sanitize::Sanitize for Pubkey {}
324
325#[cfg_attr(test, derive(strum_macros::FromRepr, strum_macros::EnumIter))]
328#[cfg_attr(feature = "serde", derive(Serialize))]
329#[derive(Debug, Clone, PartialEq, Eq)]
330pub enum ParsePubkeyError {
331 WrongSize,
332 Invalid,
333}
334
335impl ToPrimitive for ParsePubkeyError {
336 #[inline]
337 fn to_i64(&self) -> Option<i64> {
338 Some(match *self {
339 ParsePubkeyError::WrongSize => ParsePubkeyError::WrongSize as i64,
340 ParsePubkeyError::Invalid => ParsePubkeyError::Invalid as i64,
341 })
342 }
343 #[inline]
344 fn to_u64(&self) -> Option<u64> {
345 self.to_i64().map(|x| x as u64)
346 }
347}
348
349impl FromPrimitive for ParsePubkeyError {
350 #[inline]
351 fn from_i64(n: i64) -> Option<Self> {
352 if n == ParsePubkeyError::WrongSize as i64 {
353 Some(ParsePubkeyError::WrongSize)
354 } else if n == ParsePubkeyError::Invalid as i64 {
355 Some(ParsePubkeyError::Invalid)
356 } else {
357 None
358 }
359 }
360 #[inline]
361 fn from_u64(n: u64) -> Option<Self> {
362 Self::from_i64(n as i64)
363 }
364}
365
366#[cfg(feature = "std")]
367impl std::error::Error for ParsePubkeyError {}
368
369impl fmt::Display for ParsePubkeyError {
370 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
371 match self {
372 ParsePubkeyError::WrongSize => f.write_str("String is the wrong size"),
373 ParsePubkeyError::Invalid => f.write_str("Invalid Base58 string"),
374 }
375 }
376}
377
378impl From<Infallible> for ParsePubkeyError {
379 fn from(_: Infallible) -> Self {
380 unreachable!("Infallible uninhabited");
381 }
382}
383
384impl<T> DecodeError<T> for ParsePubkeyError {
385 fn type_of() -> &'static str {
386 "ParsePubkeyError"
387 }
388}
389
390impl FromStr for Pubkey {
391 type Err = ParsePubkeyError;
392
393 fn from_str(s: &str) -> Result<Self, Self::Err> {
394 if s.len() > MAX_BASE58_LEN {
395 return Err(ParsePubkeyError::WrongSize);
396 }
397 let mut bytes = [0; PUBKEY_BYTES];
398 let decoded_size = bs58::decode(s)
399 .onto(&mut bytes)
400 .map_err(|_| ParsePubkeyError::Invalid)?;
401 if decoded_size != mem::size_of::<Pubkey>() {
402 Err(ParsePubkeyError::WrongSize)
403 } else {
404 Ok(Pubkey(bytes))
405 }
406 }
407}
408
409impl From<&Pubkey> for Pubkey {
410 #[inline]
411 fn from(value: &Pubkey) -> Self {
412 *value
413 }
414}
415
416impl From<[u8; 32]> for Pubkey {
417 #[inline]
418 fn from(from: [u8; 32]) -> Self {
419 Self(from)
420 }
421}
422
423impl TryFrom<&[u8]> for Pubkey {
424 type Error = array::TryFromSliceError;
425
426 #[inline]
427 fn try_from(pubkey: &[u8]) -> Result<Self, Self::Error> {
428 <[u8; 32]>::try_from(pubkey).map(Self::from)
429 }
430}
431
432#[cfg(any(feature = "std", target_arch = "wasm32"))]
433impl TryFrom<Vec<u8>> for Pubkey {
434 type Error = Vec<u8>;
435
436 #[inline]
437 fn try_from(pubkey: Vec<u8>) -> Result<Self, Self::Error> {
438 <[u8; 32]>::try_from(pubkey).map(Self::from)
439 }
440}
441
442impl TryFrom<&str> for Pubkey {
443 type Error = ParsePubkeyError;
444 fn try_from(s: &str) -> Result<Self, Self::Error> {
445 Pubkey::from_str(s)
446 }
447}
448
449#[cfg(any(target_os = "solana", feature = "curve25519"))]
453#[allow(clippy::used_underscore_binding)]
454pub fn bytes_are_curve_point<T: AsRef<[u8]>>(_bytes: T) -> bool {
455 #[cfg(not(target_os = "solana"))]
456 {
457 let Ok(compressed_edwards_y) =
458 curve25519_dalek::edwards::CompressedEdwardsY::from_slice(_bytes.as_ref())
459 else {
460 return false;
461 };
462 compressed_edwards_y.decompress().is_some()
463 }
464 #[cfg(target_os = "solana")]
465 unimplemented!();
466}
467
468impl Pubkey {
469 pub const fn new_from_array(pubkey_array: [u8; 32]) -> Self {
470 Self(pubkey_array)
471 }
472
473 pub const fn from_str_const(s: &str) -> Self {
475 let id_array = five8_const::decode_32_const(s);
476 Pubkey::new_from_array(id_array)
477 }
478
479 pub fn new_unique() -> Self {
481 use solana_atomic_u64::AtomicU64;
482 static I: AtomicU64 = AtomicU64::new(1);
483 type T = u32;
484 const COUNTER_BYTES: usize = mem::size_of::<T>();
485 let mut b = [0u8; PUBKEY_BYTES];
486 #[cfg(any(feature = "std", target_arch = "wasm32"))]
487 let mut i = I.fetch_add(1) as T;
488 #[cfg(not(any(feature = "std", target_arch = "wasm32")))]
489 let i = I.fetch_add(1) as T;
490 b[0..COUNTER_BYTES].copy_from_slice(&i.to_be_bytes());
493 #[cfg(any(feature = "std", target_arch = "wasm32"))]
496 {
497 let mut hash = std::hash::DefaultHasher::new();
498 for slice in b[COUNTER_BYTES..].chunks_mut(COUNTER_BYTES) {
499 hash.write_u32(i);
500 i += 1;
501 slice.copy_from_slice(&hash.finish().to_ne_bytes()[0..COUNTER_BYTES]);
502 }
503 }
504 #[cfg(not(any(feature = "std", target_arch = "wasm32")))]
507 {
508 for b in b[COUNTER_BYTES..].iter_mut() {
509 *b = (i & 0xFF) as u8;
510 }
511 }
512 Self::from(b)
513 }
514
515 #[cfg(any(target_os = "solana", feature = "sha2"))]
520 pub fn create_with_seed(
521 base: &Pubkey,
522 seed: &str,
523 owner: &Pubkey,
524 ) -> Result<Pubkey, PubkeyError> {
525 if seed.len() > MAX_SEED_LEN {
526 return Err(PubkeyError::MaxSeedLengthExceeded);
527 }
528
529 let owner = owner.as_ref();
530 if owner.len() >= PDA_MARKER.len() {
531 let slice = &owner[owner.len() - PDA_MARKER.len()..];
532 if slice == PDA_MARKER {
533 return Err(PubkeyError::IllegalOwner);
534 }
535 }
536 let hash = solana_sha256_hasher::hashv(&[base.as_ref(), seed.as_ref(), owner]);
537 Ok(Pubkey::from(hash.to_bytes()))
538 }
539
540 #[cfg(any(target_os = "solana", feature = "curve25519"))]
798 pub fn find_program_address(seeds: &[&[u8]], program_id: &Pubkey) -> (Pubkey, u8) {
799 Self::try_find_program_address(seeds, program_id)
800 .unwrap_or_else(|| panic!("Unable to find a viable program address bump seed"))
801 }
802
803 #[cfg(any(target_os = "solana", feature = "curve25519"))]
820 #[allow(clippy::same_item_push)]
821 pub fn try_find_program_address(seeds: &[&[u8]], program_id: &Pubkey) -> Option<(Pubkey, u8)> {
822 #[cfg(not(target_os = "solana"))]
825 {
826 let mut bump_seed = [u8::MAX];
827 for _ in 0..u8::MAX {
828 {
829 let mut seeds_with_bump = seeds.to_vec();
830 seeds_with_bump.push(&bump_seed);
831 match Self::create_program_address(&seeds_with_bump, program_id) {
832 Ok(address) => return Some((address, bump_seed[0])),
833 Err(PubkeyError::InvalidSeeds) => (),
834 _ => break,
835 }
836 }
837 bump_seed[0] -= 1;
838 }
839 None
840 }
841 #[cfg(target_os = "solana")]
843 {
844 let mut bytes = [0; 32];
845 let mut bump_seed = u8::MAX;
846 let result = unsafe {
847 crate::syscalls::sol_try_find_program_address(
848 seeds as *const _ as *const u8,
849 seeds.len() as u64,
850 program_id as *const _ as *const u8,
851 &mut bytes as *mut _ as *mut u8,
852 &mut bump_seed as *mut _ as *mut u8,
853 )
854 };
855 match result {
856 SUCCESS => Some((Pubkey::from(bytes), bump_seed)),
857 _ => None,
858 }
859 }
860 }
861
862 #[cfg(any(target_os = "solana", feature = "curve25519"))]
909 pub fn create_program_address(
910 seeds: &[&[u8]],
911 program_id: &Pubkey,
912 ) -> Result<Pubkey, PubkeyError> {
913 if seeds.len() > MAX_SEEDS {
914 return Err(PubkeyError::MaxSeedLengthExceeded);
915 }
916 for seed in seeds.iter() {
917 if seed.len() > MAX_SEED_LEN {
918 return Err(PubkeyError::MaxSeedLengthExceeded);
919 }
920 }
921
922 #[cfg(not(target_os = "solana"))]
925 {
926 let mut hasher = solana_sha256_hasher::Hasher::default();
927 for seed in seeds.iter() {
928 hasher.hash(seed);
929 }
930 hasher.hashv(&[program_id.as_ref(), PDA_MARKER]);
931 let hash = hasher.result();
932
933 if bytes_are_curve_point(hash) {
934 return Err(PubkeyError::InvalidSeeds);
935 }
936
937 Ok(Pubkey::from(hash.to_bytes()))
938 }
939 #[cfg(target_os = "solana")]
941 {
942 let mut bytes = [0; 32];
943 let result = unsafe {
944 crate::syscalls::sol_create_program_address(
945 seeds as *const _ as *const u8,
946 seeds.len() as u64,
947 program_id as *const _ as *const u8,
948 &mut bytes as *mut _ as *mut u8,
949 )
950 };
951 match result {
952 SUCCESS => Ok(Pubkey::from(bytes)),
953 _ => Err(result.into()),
954 }
955 }
956 }
957
958 pub const fn to_bytes(self) -> [u8; 32] {
959 self.0
960 }
961
962 #[inline(always)]
964 pub const fn as_array(&self) -> &[u8; 32] {
965 &self.0
966 }
967
968 #[cfg(any(target_os = "solana", feature = "curve25519"))]
972 pub fn is_on_curve(&self) -> bool {
973 bytes_are_curve_point(self)
974 }
975
976 pub fn log(&self) {
978 #[cfg(target_os = "solana")]
979 unsafe {
980 crate::syscalls::sol_log_pubkey(self.as_ref() as *const _ as *const u8)
981 };
982
983 #[cfg(all(not(target_os = "solana"), feature = "std"))]
984 std::println!("{}", std::string::ToString::to_string(&self));
985 }
986}
987
988impl AsRef<[u8]> for Pubkey {
989 fn as_ref(&self) -> &[u8] {
990 &self.0[..]
991 }
992}
993
994impl AsMut<[u8]> for Pubkey {
995 fn as_mut(&mut self) -> &mut [u8] {
996 &mut self.0[..]
997 }
998}
999
1000fn write_as_base58(f: &mut fmt::Formatter, p: &Pubkey) -> fmt::Result {
1001 let mut out = [0u8; MAX_BASE58_LEN];
1002 let out_slice: &mut [u8] = &mut out;
1003 let len = bs58::encode(p.0).onto(out_slice).unwrap();
1006 let as_str = from_utf8(&out[..len]).unwrap();
1007 f.write_str(as_str)
1008}
1009
1010impl fmt::Debug for Pubkey {
1011 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1012 write_as_base58(f, self)
1013 }
1014}
1015
1016impl fmt::Display for Pubkey {
1017 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1018 write_as_base58(f, self)
1019 }
1020}
1021
1022#[cfg(feature = "borsh")]
1023impl borsh0_10::de::BorshDeserialize for Pubkey {
1024 fn deserialize_reader<R: borsh0_10::maybestd::io::Read>(
1025 reader: &mut R,
1026 ) -> Result<Self, borsh0_10::maybestd::io::Error> {
1027 Ok(Self(borsh0_10::BorshDeserialize::deserialize_reader(
1028 reader,
1029 )?))
1030 }
1031}
1032
1033#[cfg(feature = "borsh")]
1034macro_rules! impl_borsh_schema {
1035 ($borsh:ident) => {
1036 impl $borsh::BorshSchema for Pubkey
1037 where
1038 [u8; 32]: $borsh::BorshSchema,
1039 {
1040 fn declaration() -> $borsh::schema::Declaration {
1041 std::string::String::from("Pubkey")
1042 }
1043 fn add_definitions_recursively(
1044 definitions: &mut $borsh::maybestd::collections::HashMap<
1045 $borsh::schema::Declaration,
1046 $borsh::schema::Definition,
1047 >,
1048 ) {
1049 let fields = $borsh::schema::Fields::UnnamedFields(<[_]>::into_vec(
1050 $borsh::maybestd::boxed::Box::new([
1051 <[u8; 32] as $borsh::BorshSchema>::declaration(),
1052 ]),
1053 ));
1054 let definition = $borsh::schema::Definition::Struct { fields };
1055 <Self as $borsh::BorshSchema>::add_definition(
1056 <Self as $borsh::BorshSchema>::declaration(),
1057 definition,
1058 definitions,
1059 );
1060 <[u8; 32] as $borsh::BorshSchema>::add_definitions_recursively(definitions);
1061 }
1062 }
1063 };
1064}
1065#[cfg(feature = "borsh")]
1066impl_borsh_schema!(borsh0_10);
1067
1068#[cfg(feature = "borsh")]
1069macro_rules! impl_borsh_serialize {
1070 ($borsh:ident) => {
1071 impl $borsh::ser::BorshSerialize for Pubkey {
1072 fn serialize<W: $borsh::maybestd::io::Write>(
1073 &self,
1074 writer: &mut W,
1075 ) -> ::core::result::Result<(), $borsh::maybestd::io::Error> {
1076 $borsh::BorshSerialize::serialize(&self.0, writer)?;
1077 Ok(())
1078 }
1079 }
1080 };
1081}
1082#[cfg(feature = "borsh")]
1083impl_borsh_serialize!(borsh0_10);
1084
1085#[cfg(all(target_arch = "wasm32", feature = "curve25519"))]
1086fn js_value_to_seeds_vec(array_of_uint8_arrays: &[JsValue]) -> Result<Vec<Vec<u8>>, JsValue> {
1087 let vec_vec_u8 = array_of_uint8_arrays
1088 .iter()
1089 .filter_map(|u8_array| {
1090 u8_array
1091 .dyn_ref::<Uint8Array>()
1092 .map(|u8_array| u8_array.to_vec())
1093 })
1094 .collect::<Vec<_>>();
1095
1096 if vec_vec_u8.len() != array_of_uint8_arrays.len() {
1097 Err("Invalid Array of Uint8Arrays".into())
1098 } else {
1099 Ok(vec_vec_u8)
1100 }
1101}
1102
1103#[cfg(target_arch = "wasm32")]
1104fn display_to_jsvalue<T: fmt::Display>(display: T) -> JsValue {
1105 std::string::ToString::to_string(&display).into()
1106}
1107
1108#[allow(non_snake_case)]
1109#[cfg(target_arch = "wasm32")]
1110#[wasm_bindgen]
1111impl Pubkey {
1112 #[wasm_bindgen(constructor)]
1116 pub fn constructor(value: JsValue) -> Result<Pubkey, JsValue> {
1117 if let Some(base58_str) = value.as_string() {
1118 base58_str.parse::<Pubkey>().map_err(display_to_jsvalue)
1119 } else if let Some(uint8_array) = value.dyn_ref::<Uint8Array>() {
1120 Pubkey::try_from(uint8_array.to_vec())
1121 .map_err(|err| JsValue::from(std::format!("Invalid Uint8Array pubkey: {err:?}")))
1122 } else if let Some(array) = value.dyn_ref::<Array>() {
1123 let mut bytes = std::vec![];
1124 let iterator = js_sys::try_iter(&array.values())?.expect("array to be iterable");
1125 for x in iterator {
1126 let x = x?;
1127
1128 if let Some(n) = x.as_f64() {
1129 if n >= 0. && n <= 255. {
1130 bytes.push(n as u8);
1131 continue;
1132 }
1133 }
1134 return Err(std::format!("Invalid array argument: {:?}", x).into());
1135 }
1136 Pubkey::try_from(bytes)
1137 .map_err(|err| JsValue::from(std::format!("Invalid Array pubkey: {err:?}")))
1138 } else if value.is_undefined() {
1139 Ok(Pubkey::default())
1140 } else {
1141 Err("Unsupported argument".into())
1142 }
1143 }
1144
1145 pub fn toString(&self) -> std::string::String {
1147 std::string::ToString::to_string(self)
1148 }
1149
1150 #[cfg(feature = "curve25519")]
1152 pub fn isOnCurve(&self) -> bool {
1153 self.is_on_curve()
1154 }
1155
1156 pub fn equals(&self, other: &Pubkey) -> bool {
1158 self == other
1159 }
1160
1161 pub fn toBytes(&self) -> std::boxed::Box<[u8]> {
1163 self.0.clone().into()
1164 }
1165
1166 #[cfg(feature = "sha2")]
1168 pub fn createWithSeed(base: &Pubkey, seed: &str, owner: &Pubkey) -> Result<Pubkey, JsValue> {
1169 Pubkey::create_with_seed(base, seed, owner).map_err(display_to_jsvalue)
1170 }
1171
1172 #[cfg(feature = "curve25519")]
1174 pub fn createProgramAddress(
1175 seeds: std::boxed::Box<[JsValue]>,
1176 program_id: &Pubkey,
1177 ) -> Result<Pubkey, JsValue> {
1178 let seeds_vec = js_value_to_seeds_vec(&seeds)?;
1179 let seeds_slice = seeds_vec
1180 .iter()
1181 .map(|seed| seed.as_slice())
1182 .collect::<Vec<_>>();
1183
1184 Pubkey::create_program_address(seeds_slice.as_slice(), program_id)
1185 .map_err(display_to_jsvalue)
1186 }
1187
1188 #[cfg(feature = "curve25519")]
1193 pub fn findProgramAddress(
1194 seeds: std::boxed::Box<[JsValue]>,
1195 program_id: &Pubkey,
1196 ) -> Result<JsValue, JsValue> {
1197 let seeds_vec = js_value_to_seeds_vec(&seeds)?;
1198 let seeds_slice = seeds_vec
1199 .iter()
1200 .map(|seed| seed.as_slice())
1201 .collect::<Vec<_>>();
1202
1203 let (address, bump_seed) = Pubkey::find_program_address(seeds_slice.as_slice(), program_id);
1204
1205 let result = Array::new_with_length(2);
1206 result.set(0, address.into());
1207 result.set(1, bump_seed.into());
1208 Ok(result.into())
1209 }
1210}
1211
1212#[macro_export]
1234macro_rules! declare_id {
1235 ($address:expr) => {
1236 pub const ID: $crate::Pubkey = $crate::Pubkey::from_str_const($address);
1238
1239 pub fn check_id(id: &$crate::Pubkey) -> bool {
1243 id == &ID
1244 }
1245
1246 pub const fn id() -> $crate::Pubkey {
1248 ID
1249 }
1250
1251 #[cfg(test)]
1252 #[test]
1253 fn test_id() {
1254 assert!(check_id(&id()));
1255 }
1256 };
1257}
1258
1259#[macro_export]
1261macro_rules! declare_deprecated_id {
1262 ($address:expr) => {
1263 pub const ID: $crate::Pubkey = $crate::Pubkey::from_str_const($address);
1265
1266 #[deprecated()]
1270 pub fn check_id(id: &$crate::Pubkey) -> bool {
1271 id == &ID
1272 }
1273
1274 #[deprecated()]
1276 pub const fn id() -> $crate::Pubkey {
1277 ID
1278 }
1279
1280 #[cfg(test)]
1281 #[test]
1282 #[allow(deprecated)]
1283 fn test_id() {
1284 assert!(check_id(&id()));
1285 }
1286 };
1287}
1288
1289#[macro_export]
1305macro_rules! pubkey {
1306 ($input:literal) => {
1307 $crate::Pubkey::from_str_const($input)
1308 };
1309}
1310
1311#[cfg(all(feature = "rand", not(target_os = "solana")))]
1313pub fn new_rand() -> Pubkey {
1314 Pubkey::from(rand::random::<[u8; PUBKEY_BYTES]>())
1315}
1316
1317#[cfg(test)]
1318mod tests {
1319 use {super::*, strum::IntoEnumIterator};
1320
1321 #[test]
1322 fn test_new_unique() {
1323 assert!(Pubkey::new_unique() != Pubkey::new_unique());
1324 }
1325
1326 #[test]
1327 fn pubkey_fromstr() {
1328 let pubkey = Pubkey::new_unique();
1329 let mut pubkey_base58_str = bs58::encode(pubkey.0).into_string();
1330
1331 assert_eq!(pubkey_base58_str.parse::<Pubkey>(), Ok(pubkey));
1332
1333 pubkey_base58_str.push_str(&bs58::encode(pubkey.0).into_string());
1334 assert_eq!(
1335 pubkey_base58_str.parse::<Pubkey>(),
1336 Err(ParsePubkeyError::WrongSize)
1337 );
1338
1339 pubkey_base58_str.truncate(pubkey_base58_str.len() / 2);
1340 assert_eq!(pubkey_base58_str.parse::<Pubkey>(), Ok(pubkey));
1341
1342 pubkey_base58_str.truncate(pubkey_base58_str.len() / 2);
1343 assert_eq!(
1344 pubkey_base58_str.parse::<Pubkey>(),
1345 Err(ParsePubkeyError::WrongSize)
1346 );
1347
1348 let mut pubkey_base58_str = bs58::encode(pubkey.0).into_string();
1349 assert_eq!(pubkey_base58_str.parse::<Pubkey>(), Ok(pubkey));
1350
1351 pubkey_base58_str.replace_range(..1, "I");
1353 assert_eq!(
1354 pubkey_base58_str.parse::<Pubkey>(),
1355 Err(ParsePubkeyError::Invalid)
1356 );
1357
1358 let mut too_long = bs58::encode(&[255u8; PUBKEY_BYTES]).into_string();
1361 too_long.push('1');
1363 assert_eq!(too_long.parse::<Pubkey>(), Err(ParsePubkeyError::WrongSize));
1364 }
1365
1366 #[test]
1367 fn test_create_with_seed() {
1368 assert!(
1369 Pubkey::create_with_seed(&Pubkey::new_unique(), "☉", &Pubkey::new_unique()).is_ok()
1370 );
1371 assert_eq!(
1372 Pubkey::create_with_seed(
1373 &Pubkey::new_unique(),
1374 from_utf8(&[127; MAX_SEED_LEN + 1]).unwrap(),
1375 &Pubkey::new_unique()
1376 ),
1377 Err(PubkeyError::MaxSeedLengthExceeded)
1378 );
1379 assert!(Pubkey::create_with_seed(
1380 &Pubkey::new_unique(),
1381 "\
1382 \u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\
1383 ",
1384 &Pubkey::new_unique()
1385 )
1386 .is_ok());
1387 assert_eq!(
1389 Pubkey::create_with_seed(
1390 &Pubkey::new_unique(),
1391 "\
1392 x\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\
1393 ",
1394 &Pubkey::new_unique()
1395 ),
1396 Err(PubkeyError::MaxSeedLengthExceeded)
1397 );
1398
1399 assert!(Pubkey::create_with_seed(
1400 &Pubkey::new_unique(),
1401 from_utf8(&[0; MAX_SEED_LEN]).unwrap(),
1402 &Pubkey::new_unique(),
1403 )
1404 .is_ok());
1405
1406 assert!(
1407 Pubkey::create_with_seed(&Pubkey::new_unique(), "", &Pubkey::new_unique(),).is_ok()
1408 );
1409
1410 assert_eq!(
1411 Pubkey::create_with_seed(
1412 &Pubkey::default(),
1413 "limber chicken: 4/45",
1414 &Pubkey::default(),
1415 ),
1416 Ok("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq"
1417 .parse()
1418 .unwrap())
1419 );
1420 }
1421
1422 #[test]
1423 fn test_create_program_address() {
1424 let exceeded_seed = &[127; MAX_SEED_LEN + 1];
1425 let max_seed = &[0; MAX_SEED_LEN];
1426 let exceeded_seeds: &[&[u8]] = &[
1427 &[1],
1428 &[2],
1429 &[3],
1430 &[4],
1431 &[5],
1432 &[6],
1433 &[7],
1434 &[8],
1435 &[9],
1436 &[10],
1437 &[11],
1438 &[12],
1439 &[13],
1440 &[14],
1441 &[15],
1442 &[16],
1443 &[17],
1444 ];
1445 let max_seeds: &[&[u8]] = &[
1446 &[1],
1447 &[2],
1448 &[3],
1449 &[4],
1450 &[5],
1451 &[6],
1452 &[7],
1453 &[8],
1454 &[9],
1455 &[10],
1456 &[11],
1457 &[12],
1458 &[13],
1459 &[14],
1460 &[15],
1461 &[16],
1462 ];
1463 let program_id = Pubkey::from_str("BPFLoaderUpgradeab1e11111111111111111111111").unwrap();
1464 let public_key = Pubkey::from_str("SeedPubey1111111111111111111111111111111111").unwrap();
1465
1466 assert_eq!(
1467 Pubkey::create_program_address(&[exceeded_seed], &program_id),
1468 Err(PubkeyError::MaxSeedLengthExceeded)
1469 );
1470 assert_eq!(
1471 Pubkey::create_program_address(&[b"short_seed", exceeded_seed], &program_id),
1472 Err(PubkeyError::MaxSeedLengthExceeded)
1473 );
1474 assert!(Pubkey::create_program_address(&[max_seed], &program_id).is_ok());
1475 assert_eq!(
1476 Pubkey::create_program_address(exceeded_seeds, &program_id),
1477 Err(PubkeyError::MaxSeedLengthExceeded)
1478 );
1479 assert!(Pubkey::create_program_address(max_seeds, &program_id).is_ok());
1480 assert_eq!(
1481 Pubkey::create_program_address(&[b"", &[1]], &program_id),
1482 Ok("BwqrghZA2htAcqq8dzP1WDAhTXYTYWj7CHxF5j7TDBAe"
1483 .parse()
1484 .unwrap())
1485 );
1486 assert_eq!(
1487 Pubkey::create_program_address(&["☉".as_ref(), &[0]], &program_id),
1488 Ok("13yWmRpaTR4r5nAktwLqMpRNr28tnVUZw26rTvPSSB19"
1489 .parse()
1490 .unwrap())
1491 );
1492 assert_eq!(
1493 Pubkey::create_program_address(&[b"Talking", b"Squirrels"], &program_id),
1494 Ok("2fnQrngrQT4SeLcdToJAD96phoEjNL2man2kfRLCASVk"
1495 .parse()
1496 .unwrap())
1497 );
1498 assert_eq!(
1499 Pubkey::create_program_address(&[public_key.as_ref(), &[1]], &program_id),
1500 Ok("976ymqVnfE32QFe6NfGDctSvVa36LWnvYxhU6G2232YL"
1501 .parse()
1502 .unwrap())
1503 );
1504 assert_ne!(
1505 Pubkey::create_program_address(&[b"Talking", b"Squirrels"], &program_id).unwrap(),
1506 Pubkey::create_program_address(&[b"Talking"], &program_id).unwrap(),
1507 );
1508 }
1509
1510 #[test]
1511 fn test_pubkey_off_curve() {
1512 let mut addresses = std::vec![];
1515 for _ in 0..1_000 {
1516 let program_id = Pubkey::new_unique();
1517 let bytes1 = rand::random::<[u8; 10]>();
1518 let bytes2 = rand::random::<[u8; 32]>();
1519 if let Ok(program_address) =
1520 Pubkey::create_program_address(&[&bytes1, &bytes2], &program_id)
1521 {
1522 assert!(!program_address.is_on_curve());
1523 assert!(!addresses.contains(&program_address));
1524 addresses.push(program_address);
1525 }
1526 }
1527 }
1528
1529 #[test]
1530 fn test_find_program_address() {
1531 for _ in 0..1_000 {
1532 let program_id = Pubkey::new_unique();
1533 let (address, bump_seed) =
1534 Pubkey::find_program_address(&[b"Lil'", b"Bits"], &program_id);
1535 assert_eq!(
1536 address,
1537 Pubkey::create_program_address(&[b"Lil'", b"Bits", &[bump_seed]], &program_id)
1538 .unwrap()
1539 );
1540 }
1541 }
1542
1543 fn pubkey_from_seed_by_marker(marker: &[u8]) -> Result<Pubkey, PubkeyError> {
1544 let key = Pubkey::new_unique();
1545 let owner = Pubkey::default();
1546
1547 let mut to_fake = owner.to_bytes().to_vec();
1548 to_fake.extend_from_slice(marker);
1549
1550 let seed = from_utf8(&to_fake[..to_fake.len() - 32]).expect("not utf8");
1551 let base = &Pubkey::try_from(&to_fake[to_fake.len() - 32..]).unwrap();
1552
1553 Pubkey::create_with_seed(&key, seed, base)
1554 }
1555
1556 #[test]
1557 fn test_create_with_seed_rejects_illegal_owner() {
1558 assert_eq!(
1559 pubkey_from_seed_by_marker(PDA_MARKER),
1560 Err(PubkeyError::IllegalOwner)
1561 );
1562 assert!(pubkey_from_seed_by_marker(&PDA_MARKER[1..]).is_ok());
1563 }
1564
1565 #[test]
1566 fn test_pubkey_error_from_primitive_exhaustive() {
1567 for variant in PubkeyError::iter() {
1568 let variant_i64 = variant.clone() as i64;
1569 assert_eq!(
1570 PubkeyError::from_repr(variant_i64 as usize),
1571 PubkeyError::from_i64(variant_i64)
1572 );
1573 assert_eq!(PubkeyError::from(variant_i64 as u64), variant);
1574 }
1575 }
1576
1577 #[test]
1578 fn test_parse_pubkey_error_from_primitive_exhaustive() {
1579 for variant in ParsePubkeyError::iter() {
1580 let variant_i64 = variant as i64;
1581 assert_eq!(
1582 ParsePubkeyError::from_repr(variant_i64 as usize),
1583 ParsePubkeyError::from_i64(variant_i64)
1584 );
1585 }
1586 }
1587
1588 #[test]
1589 fn test_pubkey_macro() {
1590 const PK: Pubkey = Pubkey::from_str_const("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq");
1591 assert_eq!(pubkey!("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq"), PK);
1592 assert_eq!(
1593 Pubkey::from_str("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq").unwrap(),
1594 PK
1595 );
1596 }
1597
1598 #[test]
1599 fn test_as_array() {
1600 let bytes = [1u8; 32];
1601 let key = Pubkey::from(bytes);
1602 assert_eq!(key.as_array(), &bytes);
1603 assert_eq!(key.as_array(), &key.to_bytes());
1604 assert_eq!(key.as_array().as_ptr(), key.0.as_ptr());
1606 }
1607}