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 clone_solana_decode_error::DecodeError,
24 core::{
25 array,
26 convert::{Infallible, TryFrom},
27 fmt,
28 hash::{Hash, Hasher},
29 mem,
30 str::{from_utf8, FromStr},
31 },
32 num_traits::{FromPrimitive, ToPrimitive},
33};
34#[cfg(target_arch = "wasm32")]
35use {
36 js_sys::{Array, Uint8Array},
37 wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue},
38};
39#[cfg(target_os = "solana")]
40pub mod syscalls;
41
42pub const PUBKEY_BYTES: usize = 32;
44pub const MAX_SEED_LEN: usize = 32;
46pub const MAX_SEEDS: usize = 16;
48const MAX_BASE58_LEN: usize = 44;
50
51#[cfg(any(target_os = "solana", feature = "sha2", feature = "curve25519"))]
52const PDA_MARKER: &[u8; 21] = b"ProgramDerivedAddress";
53
54#[cfg(target_os = "solana")]
57const SUCCESS: u64 = 0;
58
59#[cfg_attr(test, derive(strum_macros::FromRepr, strum_macros::EnumIter))]
62#[cfg_attr(feature = "serde", derive(Serialize))]
63#[derive(Debug, Clone, PartialEq, Eq)]
64pub enum PubkeyError {
65 MaxSeedLengthExceeded,
67 InvalidSeeds,
68 IllegalOwner,
69}
70
71impl ToPrimitive for PubkeyError {
72 #[inline]
73 fn to_i64(&self) -> Option<i64> {
74 Some(match *self {
75 PubkeyError::MaxSeedLengthExceeded => PubkeyError::MaxSeedLengthExceeded as i64,
76 PubkeyError::InvalidSeeds => PubkeyError::InvalidSeeds as i64,
77 PubkeyError::IllegalOwner => PubkeyError::IllegalOwner as i64,
78 })
79 }
80 #[inline]
81 fn to_u64(&self) -> Option<u64> {
82 self.to_i64().map(|x| x as u64)
83 }
84}
85
86impl FromPrimitive for PubkeyError {
87 #[inline]
88 fn from_i64(n: i64) -> Option<Self> {
89 if n == PubkeyError::MaxSeedLengthExceeded as i64 {
90 Some(PubkeyError::MaxSeedLengthExceeded)
91 } else if n == PubkeyError::InvalidSeeds as i64 {
92 Some(PubkeyError::InvalidSeeds)
93 } else if n == PubkeyError::IllegalOwner as i64 {
94 Some(PubkeyError::IllegalOwner)
95 } else {
96 None
97 }
98 }
99 #[inline]
100 fn from_u64(n: u64) -> Option<Self> {
101 Self::from_i64(n as i64)
102 }
103}
104
105#[cfg(feature = "std")]
106impl std::error::Error for PubkeyError {}
107
108impl fmt::Display for PubkeyError {
109 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
110 match self {
111 PubkeyError::MaxSeedLengthExceeded => {
112 f.write_str("Length of the seed is too long for address generation")
113 }
114 PubkeyError::InvalidSeeds => {
115 f.write_str("Provided seeds do not result in a valid address")
116 }
117 PubkeyError::IllegalOwner => f.write_str("Provided owner is not allowed"),
118 }
119 }
120}
121
122impl<T> DecodeError<T> for PubkeyError {
123 fn type_of() -> &'static str {
124 "PubkeyError"
125 }
126}
127impl From<u64> for PubkeyError {
128 fn from(error: u64) -> Self {
129 match error {
130 0 => PubkeyError::MaxSeedLengthExceeded,
131 1 => PubkeyError::InvalidSeeds,
132 2 => PubkeyError::IllegalOwner,
133 _ => panic!("Unsupported PubkeyError"),
134 }
135 }
136}
137
138#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
153#[repr(transparent)]
154#[cfg_attr(
155 feature = "frozen-abi",
156 derive(clone_solana_frozen_abi_macro::AbiExample)
157)]
158#[cfg_attr(
159 feature = "borsh",
160 derive(BorshSerialize, BorshDeserialize),
161 borsh(crate = "borsh")
162)]
163#[cfg_attr(all(feature = "borsh", feature = "std"), derive(BorshSchema))]
164#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
165#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
166#[derive(Clone, Copy, Default, Eq, Ord, PartialEq, PartialOrd)]
167#[cfg_attr(feature = "dev-context-only-utils", derive(Arbitrary))]
168pub struct Pubkey(pub(crate) [u8; 32]);
169
170impl Hash for Pubkey {
174 fn hash<H: Hasher>(&self, state: &mut H) {
175 state.write(self.as_array());
176 }
177}
178
179#[cfg(all(feature = "rand", not(target_os = "solana")))]
180mod hasher {
181 use {
182 crate::PUBKEY_BYTES,
183 core::{
184 cell::Cell,
185 hash::{BuildHasher, Hasher},
186 },
187 rand::{thread_rng, Rng},
188 };
189
190 #[derive(Default)]
198 pub struct PubkeyHasher {
199 offset: usize,
200 state: u64,
201 }
202
203 impl Hasher for PubkeyHasher {
204 #[inline]
205 fn finish(&self) -> u64 {
206 self.state
207 }
208 #[inline]
209 fn write(&mut self, bytes: &[u8]) {
210 debug_assert_eq!(
211 bytes.len(),
212 PUBKEY_BYTES,
213 "This hasher is intended to be used with pubkeys and nothing else"
214 );
215 let chunk: &[u8; size_of::<u64>()] = bytes[self.offset..self.offset + 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 - 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 - 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 clone_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 clone_solana_atomic_u64::AtomicU64;
482 static I: AtomicU64 = AtomicU64::new(1);
483 type T = u32;
484 const COUNTER_BYTES: usize = size_of::<T>();
485 let mut b = [0u8; PUBKEY_BYTES];
486 let mut i = I.fetch_add(1) as T;
487 b[0..COUNTER_BYTES].copy_from_slice(&i.to_be_bytes());
490 #[cfg(any(feature = "std", target_arch = "wasm32"))]
493 {
494 extern crate std;
495 let mut hash = std::hash::DefaultHasher::new();
496 for slice in b[COUNTER_BYTES..].chunks_mut(COUNTER_BYTES) {
497 hash.write_u32(i);
498 i += 1;
499 slice.copy_from_slice(&hash.finish().to_ne_bytes()[0..COUNTER_BYTES]);
500 }
501 }
502 #[cfg(not(any(feature = "std", target_arch = "wasm32")))]
505 {
506 for b in b[COUNTER_BYTES..].iter_mut() {
507 *b = (i & 0xFF) as u8;
508 }
509 }
510 Self::from(b)
511 }
512
513 #[cfg(any(target_os = "solana", feature = "sha2"))]
518 pub fn create_with_seed(
519 base: &Pubkey,
520 seed: &str,
521 owner: &Pubkey,
522 ) -> Result<Pubkey, PubkeyError> {
523 if seed.len() > MAX_SEED_LEN {
524 return Err(PubkeyError::MaxSeedLengthExceeded);
525 }
526
527 let owner = owner.as_ref();
528 if owner.len() >= PDA_MARKER.len() {
529 let slice = &owner[owner.len() - PDA_MARKER.len()..];
530 if slice == PDA_MARKER {
531 return Err(PubkeyError::IllegalOwner);
532 }
533 }
534 let hash = clone_solana_sha256_hasher::hashv(&[base.as_ref(), seed.as_ref(), owner]);
535 Ok(Pubkey::from(hash.to_bytes()))
536 }
537
538 #[cfg(any(target_os = "solana", feature = "curve25519"))]
796 pub fn find_program_address(seeds: &[&[u8]], program_id: &Pubkey) -> (Pubkey, u8) {
797 Self::try_find_program_address(seeds, program_id)
798 .unwrap_or_else(|| panic!("Unable to find a viable program address bump seed"))
799 }
800
801 #[cfg(any(target_os = "solana", feature = "curve25519"))]
818 #[allow(clippy::same_item_push)]
819 pub fn try_find_program_address(seeds: &[&[u8]], program_id: &Pubkey) -> Option<(Pubkey, u8)> {
820 #[cfg(not(target_os = "solana"))]
823 {
824 let mut bump_seed = [u8::MAX];
825 for _ in 0..u8::MAX {
826 {
827 let mut seeds_with_bump = seeds.to_vec();
828 seeds_with_bump.push(&bump_seed);
829 match Self::create_program_address(&seeds_with_bump, program_id) {
830 Ok(address) => return Some((address, bump_seed[0])),
831 Err(PubkeyError::InvalidSeeds) => (),
832 _ => break,
833 }
834 }
835 bump_seed[0] -= 1;
836 }
837 None
838 }
839 #[cfg(target_os = "solana")]
841 {
842 let mut bytes = [0; 32];
843 let mut bump_seed = u8::MAX;
844 let result = unsafe {
845 crate::syscalls::sol_try_find_program_address(
846 seeds as *const _ as *const u8,
847 seeds.len() as u64,
848 program_id as *const _ as *const u8,
849 &mut bytes as *mut _ as *mut u8,
850 &mut bump_seed as *mut _ as *mut u8,
851 )
852 };
853 match result {
854 SUCCESS => Some((Pubkey::from(bytes), bump_seed)),
855 _ => None,
856 }
857 }
858 }
859
860 #[cfg(any(target_os = "solana", feature = "curve25519"))]
907 pub fn create_program_address(
908 seeds: &[&[u8]],
909 program_id: &Pubkey,
910 ) -> Result<Pubkey, PubkeyError> {
911 if seeds.len() > MAX_SEEDS {
912 return Err(PubkeyError::MaxSeedLengthExceeded);
913 }
914 for seed in seeds.iter() {
915 if seed.len() > MAX_SEED_LEN {
916 return Err(PubkeyError::MaxSeedLengthExceeded);
917 }
918 }
919
920 #[cfg(not(target_os = "solana"))]
923 {
924 let mut hasher = clone_solana_sha256_hasher::Hasher::default();
925 for seed in seeds.iter() {
926 hasher.hash(seed);
927 }
928 hasher.hashv(&[program_id.as_ref(), PDA_MARKER]);
929 let hash = hasher.result();
930
931 if bytes_are_curve_point(hash) {
932 return Err(PubkeyError::InvalidSeeds);
933 }
934
935 Ok(Pubkey::from(hash.to_bytes()))
936 }
937 #[cfg(target_os = "solana")]
939 {
940 let mut bytes = [0; 32];
941 let result = unsafe {
942 crate::syscalls::sol_create_program_address(
943 seeds as *const _ as *const u8,
944 seeds.len() as u64,
945 program_id as *const _ as *const u8,
946 &mut bytes as *mut _ as *mut u8,
947 )
948 };
949 match result {
950 SUCCESS => Ok(Pubkey::from(bytes)),
951 _ => Err(result.into()),
952 }
953 }
954 }
955
956 pub const fn to_bytes(self) -> [u8; 32] {
957 self.0
958 }
959
960 #[inline(always)]
962 pub const fn as_array(&self) -> &[u8; 32] {
963 &self.0
964 }
965
966 #[cfg(any(target_os = "solana", feature = "curve25519"))]
970 pub fn is_on_curve(&self) -> bool {
971 bytes_are_curve_point(self)
972 }
973
974 pub fn log(&self) {
976 #[cfg(target_os = "solana")]
977 unsafe {
978 crate::syscalls::sol_log_pubkey(self.as_ref() as *const _ as *const u8)
979 };
980
981 #[cfg(all(not(target_os = "solana"), feature = "std"))]
982 std::println!("{}", std::string::ToString::to_string(&self));
983 }
984}
985
986impl AsRef<[u8]> for Pubkey {
987 fn as_ref(&self) -> &[u8] {
988 &self.0[..]
989 }
990}
991
992impl AsMut<[u8]> for Pubkey {
993 fn as_mut(&mut self) -> &mut [u8] {
994 &mut self.0[..]
995 }
996}
997
998fn write_as_base58(f: &mut fmt::Formatter, p: &Pubkey) -> fmt::Result {
999 let mut out = [0u8; MAX_BASE58_LEN];
1000 let out_slice: &mut [u8] = &mut out;
1001 let len = bs58::encode(p.0).onto(out_slice).unwrap();
1004 let as_str = from_utf8(&out[..len]).unwrap();
1005 f.write_str(as_str)
1006}
1007
1008impl fmt::Debug for Pubkey {
1009 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1010 write_as_base58(f, self)
1011 }
1012}
1013
1014impl fmt::Display for Pubkey {
1015 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1016 write_as_base58(f, self)
1017 }
1018}
1019
1020#[cfg(feature = "borsh")]
1021impl borsh0_10::de::BorshDeserialize for Pubkey {
1022 fn deserialize_reader<R: borsh0_10::maybestd::io::Read>(
1023 reader: &mut R,
1024 ) -> Result<Self, borsh0_10::maybestd::io::Error> {
1025 Ok(Self(borsh0_10::BorshDeserialize::deserialize_reader(
1026 reader,
1027 )?))
1028 }
1029}
1030
1031#[cfg(feature = "borsh")]
1032macro_rules! impl_borsh_schema {
1033 ($borsh:ident) => {
1034 impl $borsh::BorshSchema for Pubkey
1035 where
1036 [u8; 32]: $borsh::BorshSchema,
1037 {
1038 fn declaration() -> $borsh::schema::Declaration {
1039 std::string::String::from("Pubkey")
1040 }
1041 fn add_definitions_recursively(
1042 definitions: &mut $borsh::maybestd::collections::HashMap<
1043 $borsh::schema::Declaration,
1044 $borsh::schema::Definition,
1045 >,
1046 ) {
1047 let fields = $borsh::schema::Fields::UnnamedFields(<[_]>::into_vec(
1048 $borsh::maybestd::boxed::Box::new([
1049 <[u8; 32] as $borsh::BorshSchema>::declaration(),
1050 ]),
1051 ));
1052 let definition = $borsh::schema::Definition::Struct { fields };
1053 <Self as $borsh::BorshSchema>::add_definition(
1054 <Self as $borsh::BorshSchema>::declaration(),
1055 definition,
1056 definitions,
1057 );
1058 <[u8; 32] as $borsh::BorshSchema>::add_definitions_recursively(definitions);
1059 }
1060 }
1061 };
1062}
1063#[cfg(feature = "borsh")]
1064impl_borsh_schema!(borsh0_10);
1065
1066#[cfg(feature = "borsh")]
1067macro_rules! impl_borsh_serialize {
1068 ($borsh:ident) => {
1069 impl $borsh::ser::BorshSerialize for Pubkey {
1070 fn serialize<W: $borsh::maybestd::io::Write>(
1071 &self,
1072 writer: &mut W,
1073 ) -> ::core::result::Result<(), $borsh::maybestd::io::Error> {
1074 $borsh::BorshSerialize::serialize(&self.0, writer)?;
1075 Ok(())
1076 }
1077 }
1078 };
1079}
1080#[cfg(feature = "borsh")]
1081impl_borsh_serialize!(borsh0_10);
1082
1083#[cfg(all(target_arch = "wasm32", feature = "curve25519"))]
1084fn js_value_to_seeds_vec(array_of_uint8_arrays: &[JsValue]) -> Result<Vec<Vec<u8>>, JsValue> {
1085 let vec_vec_u8 = array_of_uint8_arrays
1086 .iter()
1087 .filter_map(|u8_array| {
1088 u8_array
1089 .dyn_ref::<Uint8Array>()
1090 .map(|u8_array| u8_array.to_vec())
1091 })
1092 .collect::<Vec<_>>();
1093
1094 if vec_vec_u8.len() != array_of_uint8_arrays.len() {
1095 Err("Invalid Array of Uint8Arrays".into())
1096 } else {
1097 Ok(vec_vec_u8)
1098 }
1099}
1100
1101#[cfg(target_arch = "wasm32")]
1102fn display_to_jsvalue<T: fmt::Display>(display: T) -> JsValue {
1103 std::string::ToString::to_string(&display).into()
1104}
1105
1106#[allow(non_snake_case)]
1107#[cfg(target_arch = "wasm32")]
1108#[wasm_bindgen]
1109impl Pubkey {
1110 #[wasm_bindgen(constructor)]
1114 pub fn constructor(value: JsValue) -> Result<Pubkey, JsValue> {
1115 if let Some(base58_str) = value.as_string() {
1116 base58_str.parse::<Pubkey>().map_err(display_to_jsvalue)
1117 } else if let Some(uint8_array) = value.dyn_ref::<Uint8Array>() {
1118 Pubkey::try_from(uint8_array.to_vec())
1119 .map_err(|err| JsValue::from(std::format!("Invalid Uint8Array pubkey: {err:?}")))
1120 } else if let Some(array) = value.dyn_ref::<Array>() {
1121 let mut bytes = std::vec![];
1122 let iterator = js_sys::try_iter(&array.values())?.expect("array to be iterable");
1123 for x in iterator {
1124 let x = x?;
1125
1126 if let Some(n) = x.as_f64() {
1127 if n >= 0. && n <= 255. {
1128 bytes.push(n as u8);
1129 continue;
1130 }
1131 }
1132 return Err(std::format!("Invalid array argument: {:?}", x).into());
1133 }
1134 Pubkey::try_from(bytes)
1135 .map_err(|err| JsValue::from(std::format!("Invalid Array pubkey: {err:?}")))
1136 } else if value.is_undefined() {
1137 Ok(Pubkey::default())
1138 } else {
1139 Err("Unsupported argument".into())
1140 }
1141 }
1142
1143 pub fn toString(&self) -> std::string::String {
1145 std::string::ToString::to_string(self)
1146 }
1147
1148 #[cfg(feature = "curve25519")]
1150 pub fn isOnCurve(&self) -> bool {
1151 self.is_on_curve()
1152 }
1153
1154 pub fn equals(&self, other: &Pubkey) -> bool {
1156 self == other
1157 }
1158
1159 pub fn toBytes(&self) -> std::boxed::Box<[u8]> {
1161 self.0.clone().into()
1162 }
1163
1164 #[cfg(feature = "sha2")]
1166 pub fn createWithSeed(base: &Pubkey, seed: &str, owner: &Pubkey) -> Result<Pubkey, JsValue> {
1167 Pubkey::create_with_seed(base, seed, owner).map_err(display_to_jsvalue)
1168 }
1169
1170 #[cfg(feature = "curve25519")]
1172 pub fn createProgramAddress(
1173 seeds: std::boxed::Box<[JsValue]>,
1174 program_id: &Pubkey,
1175 ) -> Result<Pubkey, JsValue> {
1176 let seeds_vec = js_value_to_seeds_vec(&seeds)?;
1177 let seeds_slice = seeds_vec
1178 .iter()
1179 .map(|seed| seed.as_slice())
1180 .collect::<Vec<_>>();
1181
1182 Pubkey::create_program_address(seeds_slice.as_slice(), program_id)
1183 .map_err(display_to_jsvalue)
1184 }
1185
1186 #[cfg(feature = "curve25519")]
1191 pub fn findProgramAddress(
1192 seeds: std::boxed::Box<[JsValue]>,
1193 program_id: &Pubkey,
1194 ) -> Result<JsValue, JsValue> {
1195 let seeds_vec = js_value_to_seeds_vec(&seeds)?;
1196 let seeds_slice = seeds_vec
1197 .iter()
1198 .map(|seed| seed.as_slice())
1199 .collect::<Vec<_>>();
1200
1201 let (address, bump_seed) = Pubkey::find_program_address(seeds_slice.as_slice(), program_id);
1202
1203 let result = Array::new_with_length(2);
1204 result.set(0, address.into());
1205 result.set(1, bump_seed.into());
1206 Ok(result.into())
1207 }
1208}
1209
1210#[macro_export]
1232macro_rules! declare_id {
1233 ($address:expr) => {
1234 pub const ID: $crate::Pubkey = $crate::Pubkey::from_str_const($address);
1236
1237 pub fn check_id(id: &$crate::Pubkey) -> bool {
1241 id == &ID
1242 }
1243
1244 pub const fn id() -> $crate::Pubkey {
1246 ID
1247 }
1248
1249 #[cfg(test)]
1250 #[test]
1251 fn test_id() {
1252 assert!(check_id(&id()));
1253 }
1254 };
1255}
1256
1257#[macro_export]
1259macro_rules! declare_deprecated_id {
1260 ($address:expr) => {
1261 pub const ID: $crate::Pubkey = $crate::Pubkey::from_str_const($address);
1263
1264 #[deprecated()]
1268 pub fn check_id(id: &$crate::Pubkey) -> bool {
1269 id == &ID
1270 }
1271
1272 #[deprecated()]
1274 pub const fn id() -> $crate::Pubkey {
1275 ID
1276 }
1277
1278 #[cfg(test)]
1279 #[test]
1280 #[allow(deprecated)]
1281 fn test_id() {
1282 assert!(check_id(&id()));
1283 }
1284 };
1285}
1286
1287#[macro_export]
1303macro_rules! pubkey {
1304 ($input:literal) => {
1305 $crate::Pubkey::from_str_const($input)
1306 };
1307}
1308
1309#[cfg(all(feature = "rand", not(target_os = "solana")))]
1311pub fn new_rand() -> Pubkey {
1312 Pubkey::from(rand::random::<[u8; PUBKEY_BYTES]>())
1313}
1314
1315#[cfg(test)]
1316mod tests {
1317 use {super::*, strum::IntoEnumIterator};
1318
1319 #[test]
1320 fn test_new_unique() {
1321 assert!(Pubkey::new_unique() != Pubkey::new_unique());
1322 }
1323
1324 #[test]
1325 fn pubkey_fromstr() {
1326 let pubkey = Pubkey::new_unique();
1327 let mut pubkey_base58_str = bs58::encode(pubkey.0).into_string();
1328
1329 assert_eq!(pubkey_base58_str.parse::<Pubkey>(), Ok(pubkey));
1330
1331 pubkey_base58_str.push_str(&bs58::encode(pubkey.0).into_string());
1332 assert_eq!(
1333 pubkey_base58_str.parse::<Pubkey>(),
1334 Err(ParsePubkeyError::WrongSize)
1335 );
1336
1337 pubkey_base58_str.truncate(pubkey_base58_str.len() / 2);
1338 assert_eq!(pubkey_base58_str.parse::<Pubkey>(), Ok(pubkey));
1339
1340 pubkey_base58_str.truncate(pubkey_base58_str.len() / 2);
1341 assert_eq!(
1342 pubkey_base58_str.parse::<Pubkey>(),
1343 Err(ParsePubkeyError::WrongSize)
1344 );
1345
1346 let mut pubkey_base58_str = bs58::encode(pubkey.0).into_string();
1347 assert_eq!(pubkey_base58_str.parse::<Pubkey>(), Ok(pubkey));
1348
1349 pubkey_base58_str.replace_range(..1, "I");
1351 assert_eq!(
1352 pubkey_base58_str.parse::<Pubkey>(),
1353 Err(ParsePubkeyError::Invalid)
1354 );
1355
1356 let mut too_long = bs58::encode(&[255u8; PUBKEY_BYTES]).into_string();
1359 too_long.push('1');
1361 assert_eq!(too_long.parse::<Pubkey>(), Err(ParsePubkeyError::WrongSize));
1362 }
1363
1364 #[test]
1365 fn test_create_with_seed() {
1366 assert!(
1367 Pubkey::create_with_seed(&Pubkey::new_unique(), "☉", &Pubkey::new_unique()).is_ok()
1368 );
1369 assert_eq!(
1370 Pubkey::create_with_seed(
1371 &Pubkey::new_unique(),
1372 from_utf8(&[127; MAX_SEED_LEN + 1]).unwrap(),
1373 &Pubkey::new_unique()
1374 ),
1375 Err(PubkeyError::MaxSeedLengthExceeded)
1376 );
1377 assert!(Pubkey::create_with_seed(
1378 &Pubkey::new_unique(),
1379 "\
1380 \u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\
1381 ",
1382 &Pubkey::new_unique()
1383 )
1384 .is_ok());
1385 assert_eq!(
1387 Pubkey::create_with_seed(
1388 &Pubkey::new_unique(),
1389 "\
1390 x\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\
1391 ",
1392 &Pubkey::new_unique()
1393 ),
1394 Err(PubkeyError::MaxSeedLengthExceeded)
1395 );
1396
1397 assert!(Pubkey::create_with_seed(
1398 &Pubkey::new_unique(),
1399 from_utf8(&[0; MAX_SEED_LEN]).unwrap(),
1400 &Pubkey::new_unique(),
1401 )
1402 .is_ok());
1403
1404 assert!(
1405 Pubkey::create_with_seed(&Pubkey::new_unique(), "", &Pubkey::new_unique(),).is_ok()
1406 );
1407
1408 assert_eq!(
1409 Pubkey::create_with_seed(
1410 &Pubkey::default(),
1411 "limber chicken: 4/45",
1412 &Pubkey::default(),
1413 ),
1414 Ok("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq"
1415 .parse()
1416 .unwrap())
1417 );
1418 }
1419
1420 #[test]
1421 fn test_create_program_address() {
1422 let exceeded_seed = &[127; MAX_SEED_LEN + 1];
1423 let max_seed = &[0; MAX_SEED_LEN];
1424 let exceeded_seeds: &[&[u8]] = &[
1425 &[1],
1426 &[2],
1427 &[3],
1428 &[4],
1429 &[5],
1430 &[6],
1431 &[7],
1432 &[8],
1433 &[9],
1434 &[10],
1435 &[11],
1436 &[12],
1437 &[13],
1438 &[14],
1439 &[15],
1440 &[16],
1441 &[17],
1442 ];
1443 let max_seeds: &[&[u8]] = &[
1444 &[1],
1445 &[2],
1446 &[3],
1447 &[4],
1448 &[5],
1449 &[6],
1450 &[7],
1451 &[8],
1452 &[9],
1453 &[10],
1454 &[11],
1455 &[12],
1456 &[13],
1457 &[14],
1458 &[15],
1459 &[16],
1460 ];
1461 let program_id = Pubkey::from_str("BPFLoaderUpgradeab1e11111111111111111111111").unwrap();
1462 let public_key = Pubkey::from_str("SeedPubey1111111111111111111111111111111111").unwrap();
1463
1464 assert_eq!(
1465 Pubkey::create_program_address(&[exceeded_seed], &program_id),
1466 Err(PubkeyError::MaxSeedLengthExceeded)
1467 );
1468 assert_eq!(
1469 Pubkey::create_program_address(&[b"short_seed", exceeded_seed], &program_id),
1470 Err(PubkeyError::MaxSeedLengthExceeded)
1471 );
1472 assert!(Pubkey::create_program_address(&[max_seed], &program_id).is_ok());
1473 assert_eq!(
1474 Pubkey::create_program_address(exceeded_seeds, &program_id),
1475 Err(PubkeyError::MaxSeedLengthExceeded)
1476 );
1477 assert!(Pubkey::create_program_address(max_seeds, &program_id).is_ok());
1478 assert_eq!(
1479 Pubkey::create_program_address(&[b"", &[1]], &program_id),
1480 Ok("BwqrghZA2htAcqq8dzP1WDAhTXYTYWj7CHxF5j7TDBAe"
1481 .parse()
1482 .unwrap())
1483 );
1484 assert_eq!(
1485 Pubkey::create_program_address(&["☉".as_ref(), &[0]], &program_id),
1486 Ok("13yWmRpaTR4r5nAktwLqMpRNr28tnVUZw26rTvPSSB19"
1487 .parse()
1488 .unwrap())
1489 );
1490 assert_eq!(
1491 Pubkey::create_program_address(&[b"Talking", b"Squirrels"], &program_id),
1492 Ok("2fnQrngrQT4SeLcdToJAD96phoEjNL2man2kfRLCASVk"
1493 .parse()
1494 .unwrap())
1495 );
1496 assert_eq!(
1497 Pubkey::create_program_address(&[public_key.as_ref(), &[1]], &program_id),
1498 Ok("976ymqVnfE32QFe6NfGDctSvVa36LWnvYxhU6G2232YL"
1499 .parse()
1500 .unwrap())
1501 );
1502 assert_ne!(
1503 Pubkey::create_program_address(&[b"Talking", b"Squirrels"], &program_id).unwrap(),
1504 Pubkey::create_program_address(&[b"Talking"], &program_id).unwrap(),
1505 );
1506 }
1507
1508 #[test]
1509 fn test_pubkey_off_curve() {
1510 let mut addresses = std::vec![];
1513 for _ in 0..1_000 {
1514 let program_id = Pubkey::new_unique();
1515 let bytes1 = rand::random::<[u8; 10]>();
1516 let bytes2 = rand::random::<[u8; 32]>();
1517 if let Ok(program_address) =
1518 Pubkey::create_program_address(&[&bytes1, &bytes2], &program_id)
1519 {
1520 assert!(!program_address.is_on_curve());
1521 assert!(!addresses.contains(&program_address));
1522 addresses.push(program_address);
1523 }
1524 }
1525 }
1526
1527 #[test]
1528 fn test_find_program_address() {
1529 for _ in 0..1_000 {
1530 let program_id = Pubkey::new_unique();
1531 let (address, bump_seed) =
1532 Pubkey::find_program_address(&[b"Lil'", b"Bits"], &program_id);
1533 assert_eq!(
1534 address,
1535 Pubkey::create_program_address(&[b"Lil'", b"Bits", &[bump_seed]], &program_id)
1536 .unwrap()
1537 );
1538 }
1539 }
1540
1541 fn pubkey_from_seed_by_marker(marker: &[u8]) -> Result<Pubkey, PubkeyError> {
1542 let key = Pubkey::new_unique();
1543 let owner = Pubkey::default();
1544
1545 let mut to_fake = owner.to_bytes().to_vec();
1546 to_fake.extend_from_slice(marker);
1547
1548 let seed = from_utf8(&to_fake[..to_fake.len() - 32]).expect("not utf8");
1549 let base = &Pubkey::try_from(&to_fake[to_fake.len() - 32..]).unwrap();
1550
1551 Pubkey::create_with_seed(&key, seed, base)
1552 }
1553
1554 #[test]
1555 fn test_create_with_seed_rejects_illegal_owner() {
1556 assert_eq!(
1557 pubkey_from_seed_by_marker(PDA_MARKER),
1558 Err(PubkeyError::IllegalOwner)
1559 );
1560 assert!(pubkey_from_seed_by_marker(&PDA_MARKER[1..]).is_ok());
1561 }
1562
1563 #[test]
1564 fn test_pubkey_error_from_primitive_exhaustive() {
1565 for variant in PubkeyError::iter() {
1566 let variant_i64 = variant.clone() as i64;
1567 assert_eq!(
1568 PubkeyError::from_repr(variant_i64 as usize),
1569 PubkeyError::from_i64(variant_i64)
1570 );
1571 assert_eq!(PubkeyError::from(variant_i64 as u64), variant);
1572 }
1573 }
1574
1575 #[test]
1576 fn test_parse_pubkey_error_from_primitive_exhaustive() {
1577 for variant in ParsePubkeyError::iter() {
1578 let variant_i64 = variant as i64;
1579 assert_eq!(
1580 ParsePubkeyError::from_repr(variant_i64 as usize),
1581 ParsePubkeyError::from_i64(variant_i64)
1582 );
1583 }
1584 }
1585
1586 #[test]
1587 fn test_pubkey_macro() {
1588 const PK: Pubkey = Pubkey::from_str_const("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq");
1589 assert_eq!(pubkey!("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq"), PK);
1590 assert_eq!(
1591 Pubkey::from_str("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq").unwrap(),
1592 PK
1593 );
1594 }
1595
1596 #[test]
1597 fn test_as_array() {
1598 let bytes = [1u8; 32];
1599 let key = Pubkey::from(bytes);
1600 assert_eq!(key.as_array(), &bytes);
1601 assert_eq!(key.as_array(), &key.to_bytes());
1602 assert_eq!(key.as_array().as_ptr(), key.0.as_ptr());
1604 }
1605}