* Force program address off the curve (#11323)
(cherry picked from commit 03263c850a
)
* nudge
* trailing whitespace
Co-authored-by: Jack May <jack@solana.com>
Co-authored-by: Trent Nelson <trent@solana.com>
This commit is contained in:
8
Cargo.lock
generated
8
Cargo.lock
generated
@ -1156,9 +1156,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.2"
|
||||
version = "0.14.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac746a5f3bbfdadd6106868134545e684693d54d9d44f6e9588a7d54af0bf980"
|
||||
checksum = "60fb4bb6bba52f78a471264d9a3b7d026cc0af47b22cd2cffbc0b787ca003e63"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"typenum",
|
||||
@ -4180,7 +4180,7 @@ dependencies = [
|
||||
"byteorder",
|
||||
"chrono",
|
||||
"ed25519-dalek",
|
||||
"generic-array 0.14.2",
|
||||
"generic-array 0.14.3",
|
||||
"hex",
|
||||
"hmac",
|
||||
"itertools 0.9.0",
|
||||
@ -4216,7 +4216,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"curve25519-dalek",
|
||||
"ed25519-dalek",
|
||||
"generic-array 0.14.2",
|
||||
"generic-array 0.14.3",
|
||||
"hex",
|
||||
"hmac",
|
||||
"itertools 0.9.0",
|
||||
|
1839
programs/bpf/Cargo.lock
generated
1839
programs/bpf/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -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]],
|
||||
],
|
||||
)?;
|
||||
}
|
||||
|
@ -351,13 +351,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![
|
||||
@ -376,8 +375,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(
|
||||
|
@ -77,6 +77,9 @@ pub fn register_syscalls<'a>(
|
||||
) -> Result<MemoryRegion, EbpfError<BPFError>> {
|
||||
// Syscall function common across languages
|
||||
vm.register_syscall_ex("abort", syscall_abort)?;
|
||||
if invoke_context.is_cross_program_supported() {
|
||||
vm.register_syscall_ex("sol_create_program_address", syscall_create_program_address)?;
|
||||
}
|
||||
|
||||
{
|
||||
let invoke_context = Rc::new(RefCell::new(invoke_context));
|
||||
@ -362,6 +365,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])>);
|
||||
|
@ -18,6 +18,7 @@ default = [
|
||||
"assert_matches",
|
||||
"byteorder",
|
||||
"chrono",
|
||||
"curve25519-dalek",
|
||||
"generic-array",
|
||||
"memmap",
|
||||
"rand",
|
||||
@ -35,7 +36,8 @@ bs58 = "0.3.1"
|
||||
bv = { version = "0.11.1", features = ["serde"] }
|
||||
byteorder = { version = "1.3.4", optional = true }
|
||||
chrono = { version = "0.4", optional = true }
|
||||
generic-array = { version = "0.14.1", default-features = false, features = ["serde", "more_lengths"], 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"
|
||||
itertools = { version = "0.9.0" }
|
||||
|
@ -109,6 +109,10 @@ static_assert(sizeof(uint64_t) == 8);
|
||||
#define ERROR_NOT_ENOUGH_ACCOUNT_KEYS TO_BUILTIN(11)
|
||||
/** Note: Not applicable to program written in C */
|
||||
#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
|
||||
@ -389,12 +393,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
|
||||
@ -425,9 +446,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,
|
||||
|
@ -151,6 +151,14 @@ pub enum InstructionError {
|
||||
/// Cross-program invocation reentrancy not allowed for this instruction
|
||||
#[error("Cross-program invocation reentrancy not allowed for this instruction")]
|
||||
ReentrancyNotAllowed,
|
||||
|
||||
/// 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,
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{decode_error::DecodeError, instruction::InstructionError};
|
||||
use crate::{decode_error::DecodeError, instruction::InstructionError, pubkey::PubkeyError};
|
||||
use num_traits::{FromPrimitive, ToPrimitive};
|
||||
use std::convert::TryFrom;
|
||||
use thiserror::Error;
|
||||
@ -38,6 +38,10 @@ pub enum ProgramError {
|
||||
NotEnoughAccountKeys,
|
||||
#[error("Failed to borrow a reference to account data, already borrowed")]
|
||||
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 {
|
||||
@ -70,6 +74,8 @@ impl PrintProgramError for ProgramError {
|
||||
Self::UninitializedAccount => info!("Error: UninitializedAccount"),
|
||||
Self::NotEnoughAccountKeys => info!("Error: NotEnoughAccountKeys"),
|
||||
Self::AccountBorrowFailed => info!("Error: AccountBorrowFailed"),
|
||||
Self::MaxSeedLengthExceeded => info!("Error: MaxSeedLengthExceeded"),
|
||||
Self::InvalidSeeds => info!("Error: InvalidSeeds"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -94,6 +100,8 @@ const ACCOUNT_ALREADY_INITIALIZED: u64 = to_builtin!(9);
|
||||
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 {
|
||||
@ -109,6 +117,9 @@ impl From<ProgramError> for u64 {
|
||||
ProgramError::UninitializedAccount => UNINITIALIZED_ACCOUNT,
|
||||
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
|
||||
@ -134,6 +145,8 @@ impl From<u64> for ProgramError {
|
||||
UNINITIALIZED_ACCOUNT => ProgramError::UninitializedAccount,
|
||||
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),
|
||||
}
|
||||
@ -181,6 +194,8 @@ where
|
||||
UNINITIALIZED_ACCOUNT => InstructionError::UninitializedAccount,
|
||||
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 {
|
||||
@ -192,3 +207,12 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@ -17,6 +16,8 @@ pub const MAX_SEED_LEN: usize = 32;
|
||||
pub enum PubkeyError {
|
||||
#[error("length of requested seed is too long")]
|
||||
MaxSeedLengthExceeded,
|
||||
#[error("Provided seeds do not result in a valid address")]
|
||||
InvalidSeeds,
|
||||
}
|
||||
impl<T> DecodeError<T> for PubkeyError {
|
||||
fn type_of() -> &'static str {
|
||||
@ -84,6 +85,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,
|
||||
@ -96,8 +100,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"))]
|
||||
@ -256,37 +286,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";
|
||||
|
Reference in New Issue
Block a user