Force program address off the curve (#11323)
This commit is contained in:
parent
964cfb05ea
commit
03263c850a
|
@ -1885,6 +1885,7 @@ dependencies = [
|
|||
"bv",
|
||||
"byteorder 1.3.4",
|
||||
"chrono",
|
||||
"curve25519-dalek",
|
||||
"ed25519-dalek",
|
||||
"generic-array 0.14.3",
|
||||
"hex",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)}};
|
||||
|
||||
|
|
|
@ -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]]],
|
||||
)?;
|
||||
}
|
||||
|
||||
|
|
|
@ -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]],
|
||||
],
|
||||
)?;
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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<BPFError> 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<u64, EbpfError<BPFError>> {
|
||||
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::<Result<Vec<_>, EbpfError<BPFError>>>()?;
|
||||
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<Rc<RefCell<Account>>>, Vec<(&'a mut u64, &'a mut [u8])>);
|
||||
|
|
|
@ -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<Result<()>>;
|
||||
#[frozen_abi(digest = "BHtoJzwGJ1seQ2gZmtPSLLgdvq3gRZMj5mpUJsX4wGHT")]
|
||||
#[frozen_abi(digest = "3bFpd1M1YHHKFASfjUU5L9dUkg87TKuMzwUoUebwa8Pu")]
|
||||
pub type BankSlotDelta = SlotDelta<Result<()>>;
|
||||
type TransactionAccountRefCells = Vec<Rc<RefCell<Account>>>;
|
||||
type TransactionLoaderRefCells = Vec<Vec<(Pubkey, RefCell<Account>)>>;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<Pubkey, ProgramError> {
|
||||
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,
|
||||
|
|
|
@ -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<ProgramError> for u64 {
|
||||
fn from(error: ProgramError) -> Self {
|
||||
|
@ -114,6 +118,8 @@ impl From<ProgramError> 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<u64> 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<PubkeyError> for ProgramError {
|
|||
fn from(error: PubkeyError) -> Self {
|
||||
match error {
|
||||
PubkeyError::MaxSeedLengthExceeded => ProgramError::MaxSeedLengthExceeded,
|
||||
PubkeyError::InvalidSeeds => ProgramError::InvalidSeeds,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<T> DecodeError<T> 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<dyn error::Error>> {
|
||||
let filename = "test_pubkey.json";
|
||||
|
|
Loading…
Reference in New Issue