Skip to content

Commit

Permalink
feat: add ed25519/secp256k1 precompiles (#102)
Browse files Browse the repository at this point in the history
* feat: add ed25519/secp256k1 precompiles

* implement pr feedback

* replicate banking stage approach to precompiles

* don't do what banking does so we actually load the precompiles

* update change log

* follow pre-existing changelog style

* remove unused import
  • Loading branch information
OliverNChalk authored Oct 26, 2024
1 parent 0c0b46c commit 609734c
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 2 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased]

### Added

- Add `LiteSVM::with_precompiles`.

## [0.3.0] - 2024-10-12

### Added
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ rust-version = "1.75.0"
[workspace.dependencies]
bincode = "1.3"
criterion = "0.5"
ed25519-dalek = "1.0.1"
indexmap = "2.6"
itertools = "0.12"
libsecp256k1 = "0.6.0"
litesvm = { path = "svm", version = "0.3" }
log = "0.4"
serde = "1.0"
Expand Down
2 changes: 2 additions & 0 deletions svm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ thiserror.workspace = true

[dev-dependencies]
criterion.workspace = true
ed25519-dalek.workspace = true
libsecp256k1.workspace = true
serde.workspace = true
solana-program-test.workspace = true
spl-token.workspace = true
Expand Down
7 changes: 5 additions & 2 deletions svm/src/accounts_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use solana_program_runtime::{
use solana_sdk::{
account::{AccountSharedData, ReadableAccount, WritableAccount},
account_utils::StateMut,
nonce,
native_loader, nonce,
pubkey::Pubkey,
transaction::TransactionError,
};
Expand Down Expand Up @@ -81,7 +81,10 @@ impl AccountsDb {
pubkey: Pubkey,
account: AccountSharedData,
) -> Result<(), LiteSVMError> {
if account.executable() && pubkey != Pubkey::default() {
if account.executable()
&& pubkey != Pubkey::default()
&& account.owner() != &native_loader::ID
{
let loaded_program = self.load_program(&account)?;
self.programs_cache
.replenish(pubkey, Arc::new(loaded_program));
Expand Down
10 changes: 10 additions & 0 deletions svm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![doc = include_str!("../../README.md")]
use itertools::Itertools;
use log::error;
use precompiles::load_precompiles;
use solana_bpf_loader_program::syscalls::create_program_runtime_environment_v1;
use solana_compute_budget::{
compute_budget::ComputeBudget,
Expand Down Expand Up @@ -73,6 +74,7 @@ pub mod types;
mod accounts_db;
mod builtin;
mod history;
mod precompiles;
mod spl;
mod utils;

Expand Down Expand Up @@ -115,6 +117,7 @@ impl LiteSVM {
.with_builtins(None)
.with_lamports(1_000_000u64.wrapping_mul(LAMPORTS_PER_SOL))
.with_sysvars()
.with_precompiles(None)
.with_spl_programs()
.with_sigverify(true)
.with_blockhash_check(true)
Expand Down Expand Up @@ -228,6 +231,13 @@ impl LiteSVM {
self
}

pub fn with_precompiles(mut self, feature_set: Option<FeatureSet>) -> Self {
let feature_set = feature_set.unwrap_or_else(FeatureSet::all_enabled);
load_precompiles(&mut self, feature_set);

self
}

/// Returns minimum balance required to make an account with specified data length rent exempt.
pub fn minimum_balance_for_rent_exemption(&self, data_len: usize) -> u64 {
1.max(
Expand Down
25 changes: 25 additions & 0 deletions svm/src/precompiles.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use solana_sdk::{
account::{AccountSharedData, WritableAccount},
feature_set::FeatureSet,
native_loader,
precompiles::get_precompiles,
};

use crate::LiteSVM;

pub(crate) fn load_precompiles(svm: &mut LiteSVM, feature_set: FeatureSet) {
let mut account = AccountSharedData::default();
account.set_owner(native_loader::id());
account.set_lamports(1);
account.set_executable(true);

for precompile in get_precompiles() {
if precompile
.feature
.map_or(true, |feature_id| feature_set.is_active(&feature_id))
{
svm.set_account(precompile.program_id, account.clone().into())
.unwrap();
}
}
}
101 changes: 101 additions & 0 deletions svm/tests/precompiles.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
use litesvm::LiteSVM;
use solana_sdk::{
ed25519_instruction::{self, new_ed25519_instruction},
message::Message,
secp256k1_instruction::{self, new_secp256k1_instruction},
signature::Keypair,
signer::Signer,
transaction::{Transaction, TransactionError},
};

#[test_log::test]
fn ed25519_precompile_ok() {
let kp = Keypair::new();
let kp_dalek = ed25519_dalek::Keypair::from_bytes(&kp.to_bytes()).unwrap();

let mut svm = LiteSVM::new();
svm.airdrop(&kp.pubkey(), 10u64.pow(9)).unwrap();

// Act - Produce a valid ed25519 instruction.
let ix = new_ed25519_instruction(&kp_dalek, b"hello world");
let tx = Transaction::new(
&[&kp],
Message::new(&[ix], Some(&kp.pubkey())),
svm.latest_blockhash(),
);
let res = svm.send_transaction(tx);

// Assert - Transaction passes.
assert!(res.is_ok());
}

#[test_log::test]
fn ed25519_precompile_err() {
let kp = Keypair::new();
let kp_dalek = ed25519_dalek::Keypair::from_bytes(&kp.to_bytes()).unwrap();

let mut svm = LiteSVM::new();
svm.airdrop(&kp.pubkey(), 10u64.pow(9)).unwrap();

// Act - Produce an invalid ed25519 instruction.
let mut ix = new_ed25519_instruction(&kp_dalek, b"hello world");
ix.data[ed25519_instruction::DATA_START + 32] += 1;
let tx = Transaction::new(
&[&kp],
Message::new(&[ix], Some(&kp.pubkey())),
svm.latest_blockhash(),
);
let res = svm.send_transaction(tx);

// Assert - Transaction fails.
assert_eq!(
res.err().map(|fail| fail.err),
Some(TransactionError::InvalidAccountIndex)
);
}

#[test_log::test]
fn secp256k1_precompile_ok() {
let kp = Keypair::new();
let kp_secp256k1 = libsecp256k1::SecretKey::parse_slice(&[1; 32]).unwrap();

let mut svm = LiteSVM::new();
svm.airdrop(&kp.pubkey(), 10u64.pow(9)).unwrap();

// Act - Produce a valid secp256k1 instruction.
let ix = new_secp256k1_instruction(&kp_secp256k1, b"hello world");
let tx = Transaction::new(
&[&kp],
Message::new(&[ix], Some(&kp.pubkey())),
svm.latest_blockhash(),
);
let res = svm.send_transaction(tx);

// Assert - Transaction passes.
assert!(res.is_ok());
}

#[test_log::test]
fn secp256k1_precompile_err() {
let kp = Keypair::new();
let kp_secp256k1 = libsecp256k1::SecretKey::parse_slice(&[1; 32]).unwrap();

let mut svm = LiteSVM::new();
svm.airdrop(&kp.pubkey(), 10u64.pow(9)).unwrap();

// Act - Produce an invalid secp256k1 instruction.
let mut ix = new_secp256k1_instruction(&kp_secp256k1, b"hello world");
ix.data[secp256k1_instruction::DATA_START + 32] += 1;
let tx = Transaction::new(
&[&kp],
Message::new(&[ix], Some(&kp.pubkey())),
svm.latest_blockhash(),
);
let res = svm.send_transaction(tx);

// Assert - Transaction fails.
assert_eq!(
res.err().map(|fail| fail.err),
Some(TransactionError::InvalidAccountIndex)
);
}

0 comments on commit 609734c

Please sign in to comment.