Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
117 changes: 117 additions & 0 deletions sdk/src/precompiles.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
//! @brief Solana precompiled programs

#![cfg(feature = "full")]

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(true, |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