Skip to content

Commit

Permalink
Cleanup and standardize precompiles
Browse files Browse the repository at this point in the history
  • Loading branch information
jackcmay committed Sep 15, 2021
1 parent b467e7f commit 75b4661
Show file tree
Hide file tree
Showing 7 changed files with 249 additions and 157 deletions.
2 changes: 1 addition & 1 deletion programs/bpf/tests/programs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1090,7 +1090,7 @@ fn test_program_bpf_invoke_sanity() {
result.unwrap_err(),
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete)
);
}
}
}

#[cfg(feature = "bpf_rust")]
Expand Down
93 changes: 45 additions & 48 deletions sdk/src/ed25519_instruction.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,9 @@
#![cfg(feature = "full")]

use crate::{decode_error::DecodeError, instruction::Instruction};
use crate::{feature_set::FeatureSet, instruction::Instruction, precompiles::PrecompileError};
use bytemuck::{bytes_of, Pod, Zeroable};
use ed25519_dalek::{ed25519::signature::Signature, Signer, Verifier};
use thiserror::Error;

#[derive(Error, Debug, Clone, PartialEq)]
pub enum Ed25519Error {
#[error("ed25519 public key is not valid")]
InvalidPublicKey,
#[error("ed25519 signature is not valid")]
InvalidSignature,
#[error("offset not valid")]
InvalidDataOffsets,
#[error("instruction is incorrect size")]
InvalidInstructionDataSize,
}

impl<T> DecodeError<T> for Ed25519Error {
fn type_of() -> &'static str {
"Ed25519Error"
}
}
use std::sync::Arc;

pub const PUBKEY_SERIALIZED_SIZE: usize = 32;
pub const SIGNATURE_SERIALIZED_SIZE: usize = 64;
Expand Down Expand Up @@ -95,19 +77,23 @@ pub fn new_ed25519_instruction(keypair: &ed25519_dalek::Keypair, message: &[u8])
}
}

pub fn verify_signatures(data: &[u8], instruction_datas: &[&[u8]]) -> Result<(), Ed25519Error> {
pub fn verify(
data: &[u8],
instruction_datas: &[&[u8]],
_feature_set: &Arc<FeatureSet>,
) -> Result<(), PrecompileError> {
if data.len() < SIGNATURE_OFFSETS_START {
return Err(Ed25519Error::InvalidInstructionDataSize);
return Err(PrecompileError::InvalidInstructionDataSize);
}
let num_signatures = data[0] as usize;
if num_signatures == 0 && data.len() > SIGNATURE_OFFSETS_START {
return Err(Ed25519Error::InvalidInstructionDataSize);
return Err(PrecompileError::InvalidInstructionDataSize);
}
let expected_data_size = num_signatures
.saturating_mul(SIGNATURE_OFFSETS_SERIALIZED_SIZE)
.saturating_add(SIGNATURE_OFFSETS_START);
if data.len() < expected_data_size {
return Err(Ed25519Error::InvalidInstructionDataSize);
return Err(PrecompileError::InvalidInstructionDataSize);
}
for i in 0..num_signatures {
let start = i
Expand All @@ -117,23 +103,23 @@ pub fn verify_signatures(data: &[u8], instruction_datas: &[&[u8]]) -> Result<(),

// bytemuck wants structures aligned
let offsets: &Ed25519SignatureOffsets = bytemuck::try_from_bytes(&data[start..end])
.map_err(|_| Ed25519Error::InvalidDataOffsets)?;
.map_err(|_| PrecompileError::InvalidDataOffsets)?;

// Parse out signature
let signature_index = offsets.signature_instruction_index as usize;
if signature_index >= instruction_datas.len() {
return Err(Ed25519Error::InvalidDataOffsets);
return Err(PrecompileError::InvalidDataOffsets);
}
let signature_instruction = instruction_datas[signature_index];
let sig_start = offsets.signature_offset as usize;
let sig_end = sig_start.saturating_add(SIGNATURE_SERIALIZED_SIZE);
if sig_end >= signature_instruction.len() {
return Err(Ed25519Error::InvalidDataOffsets);
return Err(PrecompileError::InvalidDataOffsets);
}

let signature =
ed25519_dalek::Signature::from_bytes(&signature_instruction[sig_start..sig_end])
.map_err(|_| Ed25519Error::InvalidSignature)?;
.map_err(|_| PrecompileError::InvalidSignature)?;

// Parse out pubkey
let pubkey = get_data_slice(
Expand All @@ -144,7 +130,7 @@ pub fn verify_signatures(data: &[u8], instruction_datas: &[&[u8]]) -> Result<(),
)?;

let publickey = ed25519_dalek::PublicKey::from_bytes(pubkey)
.map_err(|_| Ed25519Error::InvalidPublicKey)?;
.map_err(|_| PrecompileError::InvalidPublicKey)?;

// Parse out message
let message = get_data_slice(
Expand All @@ -156,7 +142,7 @@ pub fn verify_signatures(data: &[u8], instruction_datas: &[&[u8]]) -> Result<(),

publickey
.verify(message, &signature)
.map_err(|_| Ed25519Error::InvalidSignature)?;
.map_err(|_| PrecompileError::InvalidSignature)?;
}
Ok(())
}
Expand All @@ -166,16 +152,16 @@ fn get_data_slice<'a>(
instruction_index: u16,
offset_start: u16,
size: usize,
) -> Result<&'a [u8], Ed25519Error> {
) -> Result<&'a [u8], PrecompileError> {
let signature_index = instruction_index as usize;
if signature_index >= instruction_datas.len() {
return Err(Ed25519Error::InvalidDataOffsets);
return Err(PrecompileError::InvalidDataOffsets);
}
let signature_instruction = &instruction_datas[signature_index];
let start = offset_start as usize;
let end = start.saturating_add(size);
if end > signature_instruction.len() {
return Err(Ed25519Error::InvalidDataOffsets);
return Err(PrecompileError::InvalidDataOffsets);
}

Ok(&instruction_datas[signature_index][start..end])
Expand All @@ -188,7 +174,7 @@ pub mod test {
fn test_case(
num_signatures: u16,
offsets: &Ed25519SignatureOffsets,
) -> Result<(), Ed25519Error> {
) -> Result<(), PrecompileError> {
assert_eq!(
bytemuck::bytes_of(offsets).len(),
SIGNATURE_OFFSETS_SERIALIZED_SIZE
Expand All @@ -198,7 +184,11 @@ pub mod test {
instruction_data[0..SIGNATURE_OFFSETS_START].copy_from_slice(bytes_of(&num_signatures));
instruction_data[SIGNATURE_OFFSETS_START..DATA_START].copy_from_slice(bytes_of(offsets));

verify_signatures(&instruction_data, &[&[0u8; 100]])
verify(
&instruction_data,
&[&[0u8; 100]],
&Arc::new(FeatureSet::all_enabled()),
)
}

#[test]
Expand All @@ -212,8 +202,12 @@ pub mod test {
instruction_data.truncate(instruction_data.len() - 1);

assert_eq!(
verify_signatures(&instruction_data, &[&[0u8; 100]]),
Err(Ed25519Error::InvalidInstructionDataSize)
verify(
&instruction_data,
&[&[0u8; 100]],
&Arc::new(FeatureSet::all_enabled()),
),
Err(PrecompileError::InvalidInstructionDataSize)
);

let offsets = Ed25519SignatureOffsets {
Expand All @@ -222,7 +216,7 @@ pub mod test {
};
assert_eq!(
test_case(1, &offsets),
Err(Ed25519Error::InvalidDataOffsets)
Err(PrecompileError::InvalidDataOffsets)
);

let offsets = Ed25519SignatureOffsets {
Expand All @@ -231,7 +225,7 @@ pub mod test {
};
assert_eq!(
test_case(1, &offsets),
Err(Ed25519Error::InvalidDataOffsets)
Err(PrecompileError::InvalidDataOffsets)
);

let offsets = Ed25519SignatureOffsets {
Expand All @@ -240,7 +234,7 @@ pub mod test {
};
assert_eq!(
test_case(1, &offsets),
Err(Ed25519Error::InvalidDataOffsets)
Err(PrecompileError::InvalidDataOffsets)
);
}

Expand All @@ -251,7 +245,10 @@ pub mod test {
message_data_size: 1,
..Ed25519SignatureOffsets::default()
};
assert_eq!(test_case(1, &offsets), Err(Ed25519Error::InvalidSignature));
assert_eq!(
test_case(1, &offsets),
Err(PrecompileError::InvalidSignature)
);

let offsets = Ed25519SignatureOffsets {
message_data_offset: 100,
Expand All @@ -260,7 +257,7 @@ pub mod test {
};
assert_eq!(
test_case(1, &offsets),
Err(Ed25519Error::InvalidDataOffsets)
Err(PrecompileError::InvalidDataOffsets)
);

let offsets = Ed25519SignatureOffsets {
Expand All @@ -270,7 +267,7 @@ pub mod test {
};
assert_eq!(
test_case(1, &offsets),
Err(Ed25519Error::InvalidDataOffsets)
Err(PrecompileError::InvalidDataOffsets)
);

let offsets = Ed25519SignatureOffsets {
Expand All @@ -280,7 +277,7 @@ pub mod test {
};
assert_eq!(
test_case(1, &offsets),
Err(Ed25519Error::InvalidDataOffsets)
Err(PrecompileError::InvalidDataOffsets)
);
}

Expand All @@ -292,7 +289,7 @@ pub mod test {
};
assert_eq!(
test_case(1, &offsets),
Err(Ed25519Error::InvalidDataOffsets)
Err(PrecompileError::InvalidDataOffsets)
);

let offsets = Ed25519SignatureOffsets {
Expand All @@ -301,7 +298,7 @@ pub mod test {
};
assert_eq!(
test_case(1, &offsets),
Err(Ed25519Error::InvalidDataOffsets)
Err(PrecompileError::InvalidDataOffsets)
);
}

Expand All @@ -313,7 +310,7 @@ pub mod test {
};
assert_eq!(
test_case(1, &offsets),
Err(Ed25519Error::InvalidDataOffsets)
Err(PrecompileError::InvalidDataOffsets)
);

let offsets = Ed25519SignatureOffsets {
Expand All @@ -322,7 +319,7 @@ pub mod test {
};
assert_eq!(
test_case(1, &offsets),
Err(Ed25519Error::InvalidDataOffsets)
Err(PrecompileError::InvalidDataOffsets)
);
}
}
1 change: 1 addition & 0 deletions sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub mod nonce_account;
pub mod nonce_keyed_account;
pub mod packet;
pub mod poh_config;
pub mod precompiles;
pub mod process_instruction;
pub mod program_utils;
pub mod pubkey;
Expand Down
115 changes: 115 additions & 0 deletions sdk/src/precompiles.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//! @brief Solana precompiled programs
use {
crate::{
decode_error::DecodeError,
feature_set::{ed25519_program_enabled, FeatureSet},
instruction::CompiledInstruction,
pubkey::Pubkey,
},
lazy_static::lazy_static,
std::sync::Arc,
thiserror::Error,
};

/// Precompile errors
#[derive(Error, Debug, Clone, PartialEq)]
pub enum PrecompileError {
#[error("public key is not valid")]
InvalidPublicKey,
#[error("id is not valid")]
InvalidRecoveryId,
#[error("signature is not valid")]
InvalidSignature,
#[error("offset not valid")]
InvalidDataOffsets,
#[error("instruction is incorrect size")]
InvalidInstructionDataSize,
}
impl<T> DecodeError<T> for PrecompileError {
fn type_of() -> &'static str {
"PrecompileError"
}
}

/// All precompiled programs must implement the `Verify` function
pub type Verify = fn(&[u8], &[&[u8]], &Arc<FeatureSet>) -> std::result::Result<(), PrecompileError>;

/// Information on a precompiled program
struct Precompile {
/// Program id
pub program_id: Pubkey,
/// Feature to enable on, `None` indicates always enabled
pub feature: Option<Pubkey>,
/// Verification function
pub verify_fn: Verify,
}
impl Precompile {
/// Creates a new `Precompile`
pub fn new(program_id: Pubkey, feature: Option<Pubkey>, verify_fn: Verify) -> Self {
Precompile {
program_id,
feature,
verify_fn,
}
}
/// Check if a program id is this precompiled program
pub fn check_id(&self, program_id: &Pubkey, feature_set: &Arc<FeatureSet>) -> bool {
self.feature.map_or(false, |f| feature_set.is_active(&f)) && self.program_id == *program_id
}
/// Verify this precompiled program
pub fn verify(
&self,
data: &[u8],
instruction_datas: &[&[u8]],
feature_set: &Arc<FeatureSet>,
) -> std::result::Result<(), PrecompileError> {
(self.verify_fn)(data, instruction_datas, feature_set)
}
}

lazy_static! {
/// The list of all precompiled programs
static ref PRECOMPILES: Vec<Precompile> = vec![
Precompile::new(
crate::secp256k1_program::id(),
None,
crate::secp256k1_instruction::verify,
),
Precompile::new(
crate::ed25519_program::id(),
Some(ed25519_program_enabled::id()),
crate::ed25519_instruction::verify,
),
];
}

/// Check if a program is a precompiled program
pub fn is_precompile(program_id: &Pubkey, feature_set: &Arc<FeatureSet>) -> bool {
PRECOMPILES
.iter()
.any(|precompile| precompile.check_id(program_id, feature_set))
}

/// Check that a program is precompiled and if so verify it
pub fn verify_if_precompile(
program_id: &Pubkey,
precompile_instruction: &CompiledInstruction,
all_instructions: &[CompiledInstruction],
feature_set: &Arc<FeatureSet>,
) -> Result<(), PrecompileError> {
for precompile in PRECOMPILES.iter() {
if precompile.check_id(program_id, feature_set) {
let instruction_datas: Vec<_> = all_instructions
.iter()
.map(|instruction| instruction.data.as_ref())
.collect();
return precompile.verify(
&precompile_instruction.data,
&instruction_datas,
feature_set,
);
}
}
Ok(())
}
Loading

0 comments on commit 75b4661

Please sign in to comment.