Force program address off the curve (#11323)
This commit is contained in:
@ -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";
|
||||
|
Reference in New Issue
Block a user