Solana Internals Part 1: What Are the Native On-Chain Programs and Why Do They Matter?

December 31, 2021

Solana has a few built-in (native on-chain) programs (e.g., system_program, spl_token, stake, vote, ed25519, etc) that provide essential instructions and are generally trusted.

In this article, we introduce the internals of these programs, and highlight some of the intricacies.

A list of native Solana programs

Every Solana program (including both native and user-deployed smart contracts) has a unique program_id, which corresponds to the program’s pubkey.

Following is a list of native Solana programs with their corresponding program_ids:

  • system_program (Program id: 11111111111111111111111111111111)
  • stake (Program id: Stake11111111111111111111111111111111111111)
  • vote (Program id: Vote111111111111111111111111111111111111111)
  • config (Program id: Config1111111111111111111111111111111111111)
  • BPFLoaderUpgradeab1e (Program id: BPFLoaderUpgradeab1e11111111111111111111111)
  • Ed25519 (Program id: Ed25519SigVerify111111111111111111111111111)
  • Secp256k1 (Program id: KeccakSecp256k11111111111111111111111111111)

In addition, spl_token and spl_associated_token_account are official Solana programs that are also frequently used, so we consider them as well:

  • spl_token (Program id: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA)
  • spl_associated_token_account (Program id: ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL)

Cross program invocation

When a program invokes another program, the callee’s program_id will be supplied to the call, typically through one of the following two functions:

  • solana_program::program::invoke
pub fn invoke(
  instruction: &Instruction,
  account_infos: &[AccountInfo<'_>]
) -> ProgramResult
  • solana_program::program::invoke_signed
pub fn invoke_signed(
  instruction: &Instruction,
  account_infos: &[AccountInfo<'_>],
  signers_seeds: &[&[&[u8]]]
) -> ProgramResult

Note: internally, invoke calls invoke_signed without signer_seeds.

The program_id is the first parameter of “instruction”, as defined below:

pub struct Instruction {
  pub program_id: Pubkey,    
  pub accounts: Vec,    
  pub data: Vec,
}

Inside invoke and invoke_signed , RefCell checking is performed first to ensure that the account RefCells are consistent with the request:

The RefCell checking can be compute unit expensive due to nested loops.

To avoid that expense, dapps may choose to use invoke_unchecked and invoke_signed_unchecked, the unchecked version of invoke and invoke_signed respectively, which do not check RefCells. However, use invoke_unchecked and invoke_signed_unchecked at your own risk: only when you are certain that the accounts used in instruction are consistent with those in the account_infos.

The Solana Native Loader and BPF loaders

In Solana, every account including Solana programs has an owner. When a Solana program is invoked in a cross program invocation, its owner is used to process the instruction (invoke or invoke_signed). For Solana programs, their owners are either the native loader or a BPF loader.

The native loader (NativeLoader1111111111111111111111111111111) is a special program that is the owner of most native Solana programs (those program_ids ended with 111111111111111111111111111).

When a native Solana program is invoked in a cross program invocation, the native loader is used to load the native program into the Solana runtime and process the instruction.

The native loader is also the owner of three BPF loaders:

  • BPFLoaderUpgradeab1e (the upgradeable Solana BPF loader. The upgradeable BPF loader is responsible for deploying, upgrading, and executing BPF programs.)
  • BPFLoader2 (The latest Solana BPF loader. The BPF loader is responsible for loading, finalizing, and executing BPF programs.)
  • BPFLoader (The original and now deprecated Solana BPF loader)

Most Solana smart contracts use Upgradeable BPF Loader to deploy the program, so their owner is BPFLoaderUpgradeab1e and they can be upgraded (by an upgrade authority set at the program deployment time).

Some Solana programs are immutable, as they are loaded by BPFLoader2 or BPFLoader.

For example, the SPL Token spl_token and the Associated Token spl_associated_token_account programs are loaded by BPFLoader2, and they are immutable.

system_program

The System Program is probably the most frequently invoked program, often called with the following two instructions:

  • system_instruction::create_account
  • system_instruction::transfer

The System Program provides several important functionalities:

  1. create new accounts
  2. allocate account data
  3. assign accounts to owning programs
  4. transfer lamports from System Program owned accounts
  5. pay transaction fees.

The System Program is the owner of all wallet accounts.

Note: only the owner of an account has write access to the account. If an account is not owned by a program, the program is only permitted to read its data and credit the account (but not debit the account).

The System Program is also the default owner of an account when the account is created by create_account. It is then allowed to transfer lamports and importantly assign account ownership, i.e., changing owner to a different program id.

spl_token

The SPL Token Program provides functions for creating and managing tokens (including both fungible and non-fungible tokens, i.e. NFTs).

spl_token is commonly used to create new tokens, mint, burn, and distribute to users. The following instructions are frequently used in Solana smart contracts:

  • Create a token account: spl_token::instruction::initialize_account
  • Create a token mint: spl_token::instruction::initialize_mint
  • Mint new tokens to an account: spl_token::instruction::mint_to
  • Transfers tokens from one account to another: spl_token::instruction::transfer
  • Burns tokens by removing them from an account: spl_token::instruction::burn
  • Approves a delegate: spl_token::instruction::approve

spl_associated_token_account

The Associated Token Program allows a user to create a main token account for each token they own. Internally, it maps the user’s wallet address to a unique associated token account for each token mint.

Specifically, to create an associated token account for the given wallet address and token mint: create_associated_token_account

stake

The Stake Program is used to create and manage accounts representing stake and rewards for validators or their delegators.

The following instructions are often used:

  • Initialize a stake with lockup and authorization information: stake::instruction::initialize
  • Authorize a key to manage stake or withdrawal: stake::instruction::authorize
  • Withdraw unstaked lamports from the stake account: stake::instruction::withdraw
  • Set stake lockup: stake::instruction::set_lockup
  • Deactivates the stake in the account: stake::instruction::deactivate_stake
  • Merge two stake accounts: stake::instruction::merge
  • Split stake off a stake account into another stake account: stake::instruction::split
  • Delegate a stake to a particular vote account: stake::instruction::delegate_stake

vote

The Vote Program is used to create and manage accounts that track validator voting state and rewards.

config

The Config Program is used to add configuration data to the chain including the list of public keys that are permitted to modify the data.

vote and config are primarily used by Solana validators, so we omit details here.

ed25519

The Ed25519 Signature Verify Program takes an ed25519 signature, public key, and message, and is used to verify if the message is signed by the (corresponding secret) private key.

By default, signatures are verified in parallel using all available CPU cores. When perf-libs are available signature verification is offloaded to the GPU.

Multiple signatures can be verified. If any of the signatures fail to verify, an error is returned.

secp256k1

The Secp256k1 Recover Program is used to recover Secp256k1 public key from a signed message (ecrecover). It is added to support Ethereum / Solana bridge.

Secp256k1 is the name of the elliptic curve used by popular blockchains (e.g., Bitcoin, Ethereum) to implement public key cryptography. All points on this curve are valid public keys.

Both Ed25519 and Secp256k1 programs are precompiled in Solana to maximize performance.

From a security perspective

It should be noted that although these native programs are generally trustworthy and (mostly) stable, to ensure security it is important to understand their assumptions and use instructions in their expected way.

We use a security case of the SPL Token program to illustrate this point.

Before spl-token v3.1.1 (released on 2021–05–18), there was a vulnerability in the token instruction code that allows invoking an arbitrary program (instead of the real spl-token program). The fixes are in this commit.

The fixes add check_program_account in all the token instruction functions to ensure the user-provided “token_program_id” is the same as the spl-token program_id (TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA).

The check_program_account function is shown below:

The root cause of this vulnerability lies in an inconsistent assumption between the expected usages of the spl-token program and its potential usages.

Therefore, to avoid such vulnerabilities (thus attacks) in general, it is important to

  • check the expected usages of these native programs
  • add necessary user checks, and
  • always use the bug-fixed versions (e.g., spl-token v3.1.1 and above).

Soteria audit

Soteria is founded by leading minds in the fields of blockchain security and software verification.

We are pleased to provide audit services to high-impact Dapps on Solana. Please visit soteria.dev or email contact@soteria.dev