From 03263c850a091528650d85f51a3353c254f0cf6d Mon Sep 17 00:00:00 2001 From: Jack May Date: Wed, 5 Aug 2020 16:35:54 -0700 Subject: [PATCH] Force program address off the curve (#11323) --- programs/bpf/Cargo.lock | 1 + programs/bpf/c/src/invoke/invoke.c | 28 ++++++--- programs/bpf/c/src/invoked/invoked.c | 9 ++- programs/bpf/rust/invoke/src/lib.rs | 18 ++++-- programs/bpf/rust/invoked/src/lib.rs | 6 +- programs/bpf/tests/programs.rs | 20 +++--- programs/bpf_loader/src/syscalls.rs | 34 +++++++++- runtime/src/bank.rs | 2 +- sdk/Cargo.toml | 2 + sdk/bpf/c/inc/solana_sdk.h | 27 +++++++- sdk/src/instruction.rs | 4 ++ sdk/src/program.rs | 33 +++++++++- sdk/src/program_error.rs | 9 +++ sdk/src/pubkey.rs | 92 ++++++++++++++++++++++++---- 14 files changed, 240 insertions(+), 45 deletions(-) diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index a9010946a1..72775d83af 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -1885,6 +1885,7 @@ dependencies = [ "bv", "byteorder 1.3.4", "chrono", + "curve25519-dalek", "ed25519-dalek", "generic-array 0.14.3", "hex", diff --git a/programs/bpf/c/src/invoke/invoke.c b/programs/bpf/c/src/invoke/invoke.c index 7d5cffe7c1..b097c153f2 100644 --- a/programs/bpf/c/src/invoke/invoke.c +++ b/programs/bpf/c/src/invoke/invoke.c @@ -30,6 +30,10 @@ extern uint64_t entrypoint(const uint8_t *input) { return ERROR_INVALID_ARGUMENT; } + uint8_t nonce1 = params.data[1]; + uint8_t nonce2 = params.data[2]; + uint8_t nonce3 = params.data[3]; + switch (params.data[0]) { case TEST_SUCCESS: { sol_log("Call system program"); @@ -81,6 +85,18 @@ extern uint64_t entrypoint(const uint8_t *input) { sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts))); } + sol_log("Test create_program_address"); + { + uint8_t seed1[] = {'Y', 'o', 'u', ' ', 'p', 'a', 's', 's', + ' ', 'b', 'u', 't', 't', 'e', 'r'}; + const SolSignerSeed seeds1[] = {{seed1, SOL_ARRAY_SIZE(seed1)}, + {&nonce1, 1}}; + SolPubkey address; + sol_assert(SUCCESS == sol_create_program_address(seeds1, SOL_ARRAY_SIZE(seeds1), + params.program_id, &address)); + sol_assert(SolPubkey_same(&address, accounts[DERIVED_KEY1_INDEX].key)); + } + sol_log("Test derived signers"); { sol_assert(!accounts[DERIVED_KEY1_INDEX].is_signer); @@ -92,19 +108,15 @@ extern uint64_t entrypoint(const uint8_t *input) { {accounts[DERIVED_KEY1_INDEX].key, true, true}, {accounts[DERIVED_KEY2_INDEX].key, true, false}, {accounts[DERIVED_KEY3_INDEX].key, false, false}}; - uint8_t data[] = {TEST_DERIVED_SIGNERS}; + uint8_t data[] = {TEST_DERIVED_SIGNERS, nonce2, nonce3}; const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key, arguments, SOL_ARRAY_SIZE(arguments), data, SOL_ARRAY_SIZE(data)}; uint8_t seed1[] = {'Y', 'o', 'u', ' ', 'p', 'a', 's', 's', ' ', 'b', 'u', 't', 't', 'e', 'r'}; - uint8_t seed2[] = {'L', 'i', 'l', '\''}; - uint8_t seed3[] = {'B', 'i', 't', 's'}; - const SolSignerSeed seeds1[] = {{seed1, SOL_ARRAY_SIZE(seed1)}}; - const SolSignerSeed seeds2[] = {{seed2, SOL_ARRAY_SIZE(seed2)}, - {seed3, SOL_ARRAY_SIZE(seed3)}}; - const SolSignerSeeds signers_seeds[] = {{seeds1, SOL_ARRAY_SIZE(seeds1)}, - {seeds2, SOL_ARRAY_SIZE(seeds2)}}; + const SolSignerSeed seeds1[] = {{seed1, SOL_ARRAY_SIZE(seed1)}, + {&nonce1, 1}}; + const SolSignerSeeds signers_seeds[] = {{seeds1, SOL_ARRAY_SIZE(seeds1)}}; sol_assert(SUCCESS == sol_invoke_signed(&instruction, accounts, SOL_ARRAY_SIZE(accounts), signers_seeds, diff --git a/programs/bpf/c/src/invoked/invoked.c b/programs/bpf/c/src/invoked/invoked.c index 8beb6207f2..ece9591a5e 100644 --- a/programs/bpf/c/src/invoked/invoked.c +++ b/programs/bpf/c/src/invoked/invoked.c @@ -92,6 +92,9 @@ extern uint64_t entrypoint(const uint8_t *input) { sol_assert(!accounts[DERIVED_KEY2_INDEX].is_signer); sol_assert(!accounts[DERIVED_KEY2_INDEX].is_signer); + uint8_t nonce2 = params.data[1]; + uint8_t nonce3 = params.data[2]; + SolAccountMeta arguments[] = { {accounts[DERIVED_KEY1_INDEX].key, true, false}, {accounts[DERIVED_KEY2_INDEX].key, true, true}, @@ -103,9 +106,11 @@ extern uint64_t entrypoint(const uint8_t *input) { uint8_t seed1[] = {'L', 'i', 'l', '\''}; uint8_t seed2[] = {'B', 'i', 't', 's'}; const SolSignerSeed seeds1[] = {{seed1, SOL_ARRAY_SIZE(seed1)}, - {seed2, SOL_ARRAY_SIZE(seed2)}}; + {seed2, SOL_ARRAY_SIZE(seed2)}, + {&nonce2, 1}}; const SolSignerSeed seeds2[] = { - {(uint8_t *)accounts[DERIVED_KEY2_INDEX].key, SIZE_PUBKEY}}; + {(uint8_t *)accounts[DERIVED_KEY2_INDEX].key, SIZE_PUBKEY}, + {&nonce3, 1}}; const SolSignerSeeds signers_seeds[] = {{seeds1, SOL_ARRAY_SIZE(seeds1)}, {seeds2, SOL_ARRAY_SIZE(seeds2)}}; diff --git a/programs/bpf/rust/invoke/src/lib.rs b/programs/bpf/rust/invoke/src/lib.rs index 97cad93ae1..fdb66f2b3e 100644 --- a/programs/bpf/rust/invoke/src/lib.rs +++ b/programs/bpf/rust/invoke/src/lib.rs @@ -10,7 +10,7 @@ use solana_sdk::{ entrypoint, entrypoint::ProgramResult, info, - program::{invoke, invoke_signed}, + program::{create_program_address, invoke, invoke_signed}, program_error::ProgramError, pubkey::Pubkey, system_instruction, @@ -34,12 +34,16 @@ const FROM_INDEX: usize = 10; entrypoint!(process_instruction); fn process_instruction( - _program_id: &Pubkey, + program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8], ) -> ProgramResult { info!("invoke Rust program"); + let nonce1 = instruction_data[1]; + let nonce2 = instruction_data[2]; + let nonce3 = instruction_data[3]; + match instruction_data[0] { TEST_SUCCESS => { info!("Call system program"); @@ -91,6 +95,12 @@ fn process_instruction( ); } + info!("Test create_program_address"); + { + let address = create_program_address(&[b"You pass butter", &[nonce1]], program_id)?; + assert_eq!(&address, accounts[DERIVED_KEY1_INDEX].key); + } + info!("Test derived signers"); { assert!(!accounts[DERIVED_KEY1_INDEX].is_signer); @@ -105,12 +115,12 @@ fn process_instruction( (accounts[DERIVED_KEY2_INDEX].key, true, false), (accounts[DERIVED_KEY3_INDEX].key, false, false), ], - vec![TEST_DERIVED_SIGNERS], + vec![TEST_DERIVED_SIGNERS, nonce2, nonce3], ); invoke_signed( &invoked_instruction, accounts, - &[&[b"You pass butter"], &[b"Lil'", b"Bits"]], + &[&[b"You pass butter", &[nonce1]]], )?; } diff --git a/programs/bpf/rust/invoked/src/lib.rs b/programs/bpf/rust/invoked/src/lib.rs index 06087effd4..11a1f11c47 100644 --- a/programs/bpf/rust/invoked/src/lib.rs +++ b/programs/bpf/rust/invoked/src/lib.rs @@ -120,6 +120,8 @@ fn process_instruction( assert!(!accounts[DERIVED_KEY2_INDEX].is_signer); assert!(!accounts[DERIVED_KEY3_INDEX].is_signer); + let nonce2 = instruction_data[1]; + let nonce3 = instruction_data[2]; let invoked_instruction = create_instruction( *accounts[INVOKED_PROGRAM_INDEX].key, &[ @@ -133,8 +135,8 @@ fn process_instruction( &invoked_instruction, accounts, &[ - &[b"Lil'", b"Bits"], - &[accounts[DERIVED_KEY2_INDEX].key.as_ref()], + &[b"Lil'", b"Bits", &[nonce2]], + &[accounts[DERIVED_KEY2_INDEX].key.as_ref(), &[nonce3]], ], )?; } diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index da8978c3c4..e3242f1c46 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -358,13 +358,12 @@ mod bpf { let account = Account::new(43, 0, &solana_sdk::system_program::id()); bank.store_account(&from_keypair.pubkey(), &account); - let derived_key1 = - Pubkey::create_program_address(&[b"You pass butter"], &invoke_program_id).unwrap(); - let derived_key2 = - Pubkey::create_program_address(&[b"Lil'", b"Bits"], &invoked_program_id).unwrap(); - let derived_key3 = - Pubkey::create_program_address(&[derived_key2.as_ref()], &invoked_program_id) - .unwrap(); + let (derived_key1, nonce1) = + Pubkey::find_program_address(&[b"You pass butter"], &invoke_program_id); + let (derived_key2, nonce2) = + Pubkey::find_program_address(&[b"Lil'", b"Bits"], &invoked_program_id); + let (derived_key3, nonce3) = + Pubkey::find_program_address(&[derived_key2.as_ref()], &invoked_program_id); let mint_pubkey = mint_keypair.pubkey(); let account_metas = vec![ @@ -383,8 +382,11 @@ mod bpf { // success cases - let instruction = - Instruction::new(invoke_program_id, &TEST_SUCCESS, account_metas.clone()); + let instruction = Instruction::new( + invoke_program_id, + &[TEST_SUCCESS, nonce1, nonce2, nonce3], + account_metas.clone(), + ); let message = Message::new(&[instruction], Some(&mint_pubkey)); assert!(bank_client .send_and_confirm_message( diff --git a/programs/bpf_loader/src/syscalls.rs b/programs/bpf_loader/src/syscalls.rs index bf8c1cfa38..6483c523a5 100644 --- a/programs/bpf_loader/src/syscalls.rs +++ b/programs/bpf_loader/src/syscalls.rs @@ -78,7 +78,6 @@ pub fn register_syscalls<'a>( vm.register_syscall_ex("abort", syscall_abort)?; vm.register_syscall_ex("sol_panic_", syscall_sol_panic)?; - vm.register_syscall_with_context_ex( "sol_log_", Box::new(SyscallLog { @@ -91,6 +90,9 @@ pub fn register_syscalls<'a>( logger: invoke_context.get_logger(), }), )?; + if invoke_context.is_cross_program_supported() { + vm.register_syscall_ex("sol_create_program_address", syscall_create_program_address)?; + } // Cross-program invocation syscalls @@ -331,6 +333,36 @@ impl SyscallObject for SyscallSolAllocFree { } } +/// Create a program address +pub fn syscall_create_program_address( + seeds_addr: u64, + seeds_len: u64, + program_id_addr: u64, + address_addr: u64, + _arg5: u64, + ro_regions: &[MemoryRegion], + rw_regions: &[MemoryRegion], +) -> Result> { + let untranslated_seeds = translate_slice!(&[&str], seeds_addr, seeds_len, ro_regions)?; + let seeds = untranslated_seeds + .iter() + .map(|untranslated_seed| { + translate_slice!( + u8, + untranslated_seed.as_ptr(), + untranslated_seed.len(), + ro_regions + ) + }) + .collect::, EbpfError>>()?; + let program_id = translate_type!(Pubkey, program_id_addr, rw_regions)?; + let new_address = + Pubkey::create_program_address(&seeds, program_id).map_err(SyscallError::BadSeeds)?; + let address = translate_slice_mut!(u8, address_addr, 32, ro_regions)?; + address.copy_from_slice(new_address.as_ref()); + Ok(0) +} + // Cross-program invocation syscalls pub type TranslatedAccounts<'a> = (Vec>>, Vec<(&'a mut u64, &'a mut [u8])>); diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index eb19b7c65a..4a60e0508e 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -76,7 +76,7 @@ pub const SECONDS_PER_YEAR: f64 = 365.25 * 24.0 * 60.0 * 60.0; pub const MAX_LEADER_SCHEDULE_STAKES: Epoch = 5; type BankStatusCache = StatusCache>; -#[frozen_abi(digest = "BHtoJzwGJ1seQ2gZmtPSLLgdvq3gRZMj5mpUJsX4wGHT")] +#[frozen_abi(digest = "3bFpd1M1YHHKFASfjUU5L9dUkg87TKuMzwUoUebwa8Pu")] pub type BankSlotDelta = SlotDelta>; type TransactionAccountRefCells = Vec>>; type TransactionLoaderRefCells = Vec)>>; diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 4121d54e2d..6eb508ee0e 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -18,6 +18,7 @@ default = [ "assert_matches", "byteorder", "chrono", + "curve25519-dalek", "generic-array", "memmap", "rand", @@ -35,6 +36,7 @@ bs58 = "0.3.1" bv = { version = "0.11.1", features = ["serde"] } byteorder = { version = "1.3.4", optional = true } chrono = { version = "0.4", optional = true } +curve25519-dalek = { version = "2.1.0", optional = true } generic-array = { version = "0.14.3", default-features = false, features = ["serde", "more_lengths"], optional = true } hex = "0.4.2" hmac = "0.7.0" diff --git a/sdk/bpf/c/inc/solana_sdk.h b/sdk/bpf/c/inc/solana_sdk.h index 9615fcabee..83f26f6ced 100644 --- a/sdk/bpf/c/inc/solana_sdk.h +++ b/sdk/bpf/c/inc/solana_sdk.h @@ -111,6 +111,8 @@ static_assert(sizeof(uint64_t) == 8); #define ERROR_ACCOUNT_BORROW_FAILED TO_BUILTIN(12) /** The length of the seed is too long for address generation */ #define MAX_SEED_LENGTH_EXCEEDED TO_BUILTIN(13) +/** Provided seeds do not result in a valid address */ +#define INVALID_SEEDS TO_BUILTIN(14) /** * Boolean type @@ -390,12 +392,29 @@ typedef struct { uint64_t len; /** Number of seeds */ } SolSignerSeeds; +/* + * Create a program address + * + * @param seeds Seed strings used to sign program accounts + * @param seeds_len Length of the seeds array + * @param Progam id of the signer + * @param Program address created, filled on return + */ +static uint64_t sol_create_program_address( + const SolSignerSeed *seeds, + int seeds_len, + const SolPubkey *program_id, + const SolPubkey *address +); + /** * Cross-program invocation * * @{ */ /* + * Invoke another program and sign for some of the keys + * * @param instruction Instruction to process * @param account_infos Accounts used by instruction * @param account_infos_len Length of account_infos array @@ -426,9 +445,11 @@ static uint64_t sol_invoke_signed( ); } /* -* @param instruction Instruction to process -* @param account_infos Accounts used by instruction -* @param account_infos_len Length of account_infos array + * Invoke another program + * + * @param instruction Instruction to process + * @param account_infos Accounts used by instruction + * @param account_infos_len Length of account_infos array */ static uint64_t sol_invoke( const SolInstruction *instruction, diff --git a/sdk/src/instruction.rs b/sdk/src/instruction.rs index 46b7cc3343..590fb45daa 100644 --- a/sdk/src/instruction.rs +++ b/sdk/src/instruction.rs @@ -155,6 +155,10 @@ pub enum InstructionError { /// Length of the seed is too long for address generation #[error("Length of the seed is too long for address generation")] MaxSeedLengthExceeded, + + /// Provided seeds do not result in a valid address + #[error("Provided seeds do not result in a valid address")] + InvalidSeeds, } impl InstructionError { diff --git a/sdk/src/program.rs b/sdk/src/program.rs index a8be01d2f0..94ba27a0ee 100644 --- a/sdk/src/program.rs +++ b/sdk/src/program.rs @@ -2,9 +2,39 @@ use crate::{ account_info::AccountInfo, entrypoint::ProgramResult, entrypoint::SUCCESS, - instruction::Instruction, + instruction::Instruction, program_error::ProgramError, pubkey::Pubkey, }; +pub fn create_program_address( + seeds: &[&[u8]], + program_id: &Pubkey, +) -> Result { + let bytes = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, + ]; + let result = unsafe { + sol_create_program_address( + seeds as *const _ as *const u8, + seeds.len() as u64, + program_id as *const _ as *const u8, + &bytes as *const _ as *const u8, + ) + }; + match result { + SUCCESS => Ok(Pubkey::new(&bytes)), + _ => Err(result.into()), + } +} +extern "C" { + fn sol_create_program_address( + seeds_addr: *const u8, + seeds_len: u64, + program_id_addr: *const u8, + address_bytes_addr: *const u8, + ) -> u64; +} + /// Invoke a cross-program instruction pub fn invoke(instruction: &Instruction, account_infos: &[AccountInfo]) -> ProgramResult { invoke_signed(instruction, account_infos, &[]) @@ -30,7 +60,6 @@ pub fn invoke_signed( _ => Err(result.into()), } } - extern "C" { fn sol_invoke_signed_rust( instruction_addr: *const u8, diff --git a/sdk/src/program_error.rs b/sdk/src/program_error.rs index a308291dd5..7cf2d9c7a2 100644 --- a/sdk/src/program_error.rs +++ b/sdk/src/program_error.rs @@ -40,6 +40,8 @@ pub enum ProgramError { AccountBorrowFailed, #[error("Length of the seed is too long for address generation")] MaxSeedLengthExceeded, + #[error("Provided seeds do not result in a valid address")] + InvalidSeeds, } pub trait PrintProgramError { @@ -73,6 +75,7 @@ impl PrintProgramError for ProgramError { Self::NotEnoughAccountKeys => info!("Error: NotEnoughAccountKeys"), Self::AccountBorrowFailed => info!("Error: AccountBorrowFailed"), Self::MaxSeedLengthExceeded => info!("Error: MaxSeedLengthExceeded"), + Self::InvalidSeeds => info!("Error: InvalidSeeds"), } } } @@ -98,6 +101,7 @@ const UNINITIALIZED_ACCOUNT: u64 = to_builtin!(10); const NOT_ENOUGH_ACCOUNT_KEYS: u64 = to_builtin!(11); const ACCOUNT_BORROW_FAILED: u64 = to_builtin!(12); const MAX_SEED_LENGTH_EXCEEDED: u64 = to_builtin!(13); +const INVALID_SEEDS: u64 = to_builtin!(14); impl From for u64 { fn from(error: ProgramError) -> Self { @@ -114,6 +118,8 @@ impl From for u64 { ProgramError::NotEnoughAccountKeys => NOT_ENOUGH_ACCOUNT_KEYS, ProgramError::AccountBorrowFailed => ACCOUNT_BORROW_FAILED, ProgramError::MaxSeedLengthExceeded => MAX_SEED_LENGTH_EXCEEDED, + ProgramError::InvalidSeeds => INVALID_SEEDS, + ProgramError::Custom(error) => { if error == 0 { CUSTOM_ZERO @@ -140,6 +146,7 @@ impl From for ProgramError { NOT_ENOUGH_ACCOUNT_KEYS => ProgramError::NotEnoughAccountKeys, ACCOUNT_BORROW_FAILED => ProgramError::AccountBorrowFailed, MAX_SEED_LENGTH_EXCEEDED => ProgramError::MaxSeedLengthExceeded, + INVALID_SEEDS => ProgramError::InvalidSeeds, CUSTOM_ZERO => ProgramError::Custom(0), _ => ProgramError::Custom(error as u32), } @@ -189,6 +196,7 @@ where NOT_ENOUGH_ACCOUNT_KEYS => InstructionError::NotEnoughAccountKeys, ACCOUNT_BORROW_FAILED => InstructionError::AccountBorrowFailed, MAX_SEED_LENGTH_EXCEEDED => InstructionError::MaxSeedLengthExceeded, + INVALID_SEEDS => InstructionError::InvalidSeeds, _ => { // A valid custom error has no bits set in the upper 32 if error >> BUILTIN_BIT_SHIFT == 0 { @@ -205,6 +213,7 @@ impl From for ProgramError { fn from(error: PubkeyError) -> Self { match error { PubkeyError::MaxSeedLengthExceeded => ProgramError::MaxSeedLengthExceeded, + PubkeyError::InvalidSeeds => ProgramError::InvalidSeeds, } } } diff --git a/sdk/src/pubkey.rs b/sdk/src/pubkey.rs index 36c739ed01..ad5d6248b9 100644 --- a/sdk/src/pubkey.rs +++ b/sdk/src/pubkey.rs @@ -1,7 +1,6 @@ -use crate::{ - decode_error::DecodeError, - hash::{hash, hashv, Hasher}, -}; +#[cfg(not(feature = "program"))] +use crate::hash::Hasher; +use crate::{decode_error::DecodeError, hash::hashv}; use num_derive::{FromPrimitive, ToPrimitive}; #[cfg(not(feature = "program"))] use std::error; @@ -18,6 +17,8 @@ pub enum PubkeyError { /// Length of the seed is too long for address generation #[error("Length of the seed is too long for address generation")] MaxSeedLengthExceeded, + #[error("Provided seeds do not result in a valid address")] + InvalidSeeds, } impl DecodeError for PubkeyError { fn type_of() -> &'static str { @@ -87,6 +88,9 @@ impl Pubkey { )) } + /// Create a program address, valid program address must not be on the + /// ed25519 curve + #[cfg(not(feature = "program"))] pub fn create_program_address( seeds: &[&[u8]], program_id: &Pubkey, @@ -99,8 +103,34 @@ impl Pubkey { hasher.hash(seed); } hasher.hashv(&[program_id.as_ref(), "ProgramDerivedAddress".as_ref()]); + let hash = hasher.result(); - Ok(Pubkey::new(hash(hasher.result().as_ref()).as_ref())) + if curve25519_dalek::edwards::CompressedEdwardsY::from_slice(hash.as_ref()) + .decompress() + .is_some() + { + return Err(PubkeyError::InvalidSeeds); + } + + Ok(Pubkey::new(hash.as_ref())) + } + + /// Find a valid program address and its corresponding nonce which must be passed + /// as an additional seed when calling `create_program_address` + #[cfg(not(feature = "program"))] + pub fn find_program_address(seeds: &[&[u8]], program_id: &Pubkey) -> (Pubkey, u8) { + let mut nonce = [255]; + for _ in 0..std::u8::MAX { + { + let mut seeds_with_nonce = seeds.to_vec(); + seeds_with_nonce.push(&nonce); + if let Ok(address) = Self::create_program_address(&seeds_with_nonce, program_id) { + return (address, nonce[0]); + } + } + nonce[0] -= 1; + } + panic!("Unable to find a viable program address nonce"); } #[cfg(not(feature = "program"))] @@ -259,37 +289,73 @@ mod tests { Pubkey::create_program_address(&[b"short_seed", exceeded_seed], &program_id), Err(PubkeyError::MaxSeedLengthExceeded) ); - assert!(Pubkey::create_program_address(&[max_seed], &Pubkey::new_rand()).is_ok()); + assert!(Pubkey::create_program_address(&[max_seed], &program_id).is_ok()); assert_eq!( - Pubkey::create_program_address(&[b""], &program_id), - Ok("CsdSsqp6Upkh2qajhZMBM8xT4GAyDNSmcV37g4pN8rsc" + Pubkey::create_program_address(&[b"", &[1]], &program_id), + Ok("3gF2KMe9KiC6FNVBmfg9i267aMPvK37FewCip4eGBFcT" .parse() .unwrap()) ); assert_eq!( Pubkey::create_program_address(&["☉".as_ref()], &program_id), - Ok("A8mYnN8Pfx7Nn6f8RoQgsPNtAGAWmmKSBCDfyDvE6sXF" + Ok("7ytmC1nT1xY4RfxCV2ZgyA7UakC93do5ZdyhdF3EtPj7" .parse() .unwrap()) ); assert_eq!( Pubkey::create_program_address(&[b"Talking", b"Squirrels"], &program_id), - Ok("CawYq8Rmj4JRR992wVnGEFUjMEkmtmcFgEL4iS1qPczu" + Ok("HwRVBufQ4haG5XSgpspwKtNd3PC9GM9m1196uJW36vds" .parse() .unwrap()) ); assert_eq!( Pubkey::create_program_address(&[public_key.as_ref()], &program_id), - Ok("4ak7qJacCKMAGP8xJtDkg2VYZh5QKExa71ijMDjZGQyb" + Ok("GUs5qLUfsEHkcMB9T38vjr18ypEhRuNWiePW2LoK4E3K" .parse() .unwrap()) ); assert_ne!( - Pubkey::create_program_address(&[b"Talking", b"Squirrels"], &program_id), - Pubkey::create_program_address(&[b"Talking"], &program_id), + Pubkey::create_program_address(&[b"Talking", b"Squirrels"], &program_id).unwrap(), + Pubkey::create_program_address(&[b"Talking"], &program_id).unwrap(), ); } + #[test] + fn test_pubkey_off_curve() { + // try a bunch of random input, all successful generated program + // addresses must land off the curve and be unique + let mut addresses = vec![]; + for _ in 0..1_000 { + let program_id = Pubkey::new_rand(); + let bytes1 = rand::random::<[u8; 10]>(); + let bytes2 = rand::random::<[u8; 32]>(); + if let Ok(program_address) = + Pubkey::create_program_address(&[&bytes1, &bytes2], &program_id) + { + let is_on_curve = curve25519_dalek::edwards::CompressedEdwardsY::from_slice( + &program_address.to_bytes(), + ) + .decompress() + .is_some(); + assert!(!is_on_curve); + assert!(!addresses.contains(&program_address)); + addresses.push(program_address); + } + } + } + + #[test] + fn test_find_program_address() { + for _ in 0..1_000 { + let program_id = Pubkey::new_rand(); + let (address, nonce) = Pubkey::find_program_address(&[b"Lil'", b"Bits"], &program_id); + assert_eq!( + address, + Pubkey::create_program_address(&[b"Lil'", b"Bits", &[nonce]], &program_id).unwrap() + ); + } + } + #[test] fn test_read_write_pubkey() -> Result<(), Box> { let filename = "test_pubkey.json";