程序

在 Solana 中,智能合约被称为程序(program)。程序是一个无状态的账户,其中包含可执行代码。这些代码被组织成称为指令(instructions)的函数。用户通过发送包含一个或多个指令交易与程序交互。一个交易可以包含来自多个程序的指令。

当程序被部署时,Solana 使用 LLVM 将其编译为可执行和链接格式 (ELF)。ELF 文件包含以 Solana 字节码格式(sBPF)编写的程序二进制文件,并存储在链上的可执行账户中。

sBPF 是 Solana 定制的 eBPF 字节码版本。

编写程序

大多数程序使用 Rust 编写,常见的开发方法有两种:

  • Anchor:Anchor 是一个为快速和简单的 Solana 开发设计的框架。它使用 Rust 宏 来减少样板代码,非常适合初学者。
  • 原生 Rust:直接使用 Rust 编写程序,不依赖任何框架。这种方法提供了更多的灵活性,但也增加了复杂性。

更新程序

修改现有程序,必须将一个账户指定为升级权限。(通常是最初部署程序的同一个账户。)如果升级权限被撤销并设置为 None,则该程序将无法再被更新。

验证程序

Solana 支持可验证构建,允许用户检查程序的链上代码是否与其公开的源代码匹配。Anchor 框架提供了内置支持来创建可验证构建。

要检查现有程序是否已验证,可以在 Solana Explorer上搜索其程序 ID。或者,您可以使用 Ellipsis Labs 的 Solana Verifiable Build CLI 独立验证链上程序。

内置程序

System Program

System Program 是唯一可以创建新账户的账户。默认情况下,所有新账户都归 System Program 所有,尽管许多账户在创建时会被分配给新的所有者。System Program 执行以下关键功能:

功能描述
新账户创建只有 System Program 可以创建新账户。
空间分配设置每个账户数据字段的字节容量。
分配程序所有权一旦 System Program 创建了一个账户,它可以将指定的程序所有者重新分配给另一个程序账户。这就是自定义程序如何接管由 System Program 创建的新账户的所有权。
转移 SOL将 lamports(SOL)从 System Accounts 转移到其他账户。

System Program 的地址是 11111111111111111111111111111111

加载器程序

每个程序都由另一个程序(其加载器)拥有。加载器用于部署、重新部署、升级或关闭程序。它们还用于完成程序并转移程序权限。

加载器程序有时被称为“BPF 加载器”。

目前有五个加载器程序,如下表所示。

加载器程序 ID备注指令链接
nativeNativeLoader1111111111111111111111111111111拥有其他四个加载器
v1BPFLoader1111111111111111111111111111111111管理指令已禁用,但程序仍然执行
v2BPFLoader2111111111111111111111111111111111管理指令已禁用,但程序仍然执行指令
v3BPFLoaderUpgradeab1e11111111111111111111111程序部署后可以更新。程序可执行文件存储在一个单独的程序数据账户中指令
v4LoaderV411111111111111111111111111111111111开发中(未发布)指令

使用 loader-v3 或 loader-v4 部署的程序在部署后可能是可修改的,这取决于其升级权限。

当部署新程序时,默认会使用最新的加载器版本。

预编译程序

除了加载器程序,Solana 还提供以下预编译程序。

验证 ed25519 签名

ed25519 程序用于验证一个或多个 ed25519 签名。

程序程序 ID描述指令
Ed25519 程序Ed25519SigVerify111111111111111111111111111验证 ed25519 签名。如果任何签名验证失败,将返回错误。指令

ed25519 程序处理一个指令。指令的第一个 u8 包含要验证的签名数量,后跟一个单字节填充。之后,为每个要验证的签名序列化以下结构。

Ed25519SignatureOffsets
struct Ed25519SignatureOffsets {
signature_offset: u16, // offset to ed25519 signature of 64 bytes
signature_instruction_index: u16, // instruction index to find signature
public_key_offset: u16, // offset to public key of 32 bytes
public_key_instruction_index: u16, // instruction index to find public key
message_data_offset: u16, // offset to start of message data
message_data_size: u16, // size of message data
message_instruction_index: u16, // index of instruction data to get message data
}
Signature verification pseudocode
process_instruction() {
for i in 0..count {
// i'th index values referenced:
instructions = &transaction.message().instructions
instruction_index = ed25519_signature_instruction_index != u16::MAX ? ed25519_signature_instruction_index : current_instruction;
signature = instructions[instruction_index].data[ed25519_signature_offset..ed25519_signature_offset + 64]
instruction_index = ed25519_pubkey_instruction_index != u16::MAX ? ed25519_pubkey_instruction_index : current_instruction;
pubkey = instructions[instruction_index].data[ed25519_pubkey_offset..ed25519_pubkey_offset + 32]
instruction_index = ed25519_message_instruction_index != u16::MAX ? ed25519_message_instruction_index : current_instruction;
message = instructions[instruction_index].data[ed25519_message_data_offset..ed25519_message_data_offset + ed25519_message_data_size]
if pubkey.verify(signature, message) != Success {
return Error
}
}
return Success
}

验证 secp256k1 恢复

secp256k1 程序用于验证 secp256k1 公钥恢复操作。

程序程序 ID描述指令
Secp256k1 程序KeccakSecp256k11111111111111111111111111111验证 secp256k1 公钥恢复操作(ecrecover)。指令

secp256k1 程序处理一个指令。指令的第一个字节包含要验证的公钥数量。之后,为每个公钥创建以下结构,然后序列化并添加到指令数据中。

Secp256k1SignatureOffsets
struct Secp256k1SignatureOffsets {
secp_signature_offset: u16, // offset to [signature,recovery_id] of 64+1 bytes
secp_signature_instruction_index: u8, // instruction index to find signature
secp_pubkey_offset: u16, // offset to ethereum_address pubkey of 20 bytes
secp_pubkey_instruction_index: u8, // instruction index to find pubkey
secp_message_data_offset: u16, // offset to start of message data
secp_message_data_size: u16, // size of message data
secp_message_instruction_index: u8, // instruction index to find message data
}
Recovery verification pseudocode
process_instruction() {
for i in 0..count {
// i'th index values referenced:
instructions = &transaction.message().instructions
signature = instructions[secp_signature_instruction_index].data[secp_signature_offset..secp_signature_offset + 64]
recovery_id = instructions[secp_signature_instruction_index].data[secp_signature_offset + 64]
ref_eth_pubkey = instructions[secp_pubkey_instruction_index].data[secp_pubkey_offset..secp_pubkey_offset + 20]
message_hash = keccak256(instructions[secp_message_instruction_index].data[secp_message_data_offset..secp_message_data_offset + secp_message_data_size])
pubkey = ecrecover(signature, recovery_id, message_hash)
eth_pubkey = keccak256(pubkey[1..])[12..]
if eth_pubkey != ref_eth_pubkey {
return Error
}
}
return Success
}

这允许用户在交易中指定任何指令数据用于签名和消息数据。通过指定一个特殊的指令 sysvar,还可以从交易本身接收数据。

交易成本将计算需要验证的签名数量,并乘以签名验证成本系数。

secp256r1 程序用于验证最多 8 个 secp256r1 签名。

程序程序 ID描述指令链接
Secp256r1 程序Secp256r1SigVerify1111111111111111111111111验证最多 8 个 secp256r1 签名。需要提供签名、公钥和消息。如果任何一个失败,则返回错误。指令

secp256r1 程序处理一个指令。指令的第一个 u8 是需要检查的签名数量,后跟一个单字节填充。之后,为每个签名创建以下结构体,然后序列化并添加到指令数据中。

Secp256r1SignatureOffsets
struct Secp256r1SignatureOffsets {
signature_offset: u16, // offset to compact secp256r1 signature of 64 bytes
signature_instruction_index: u16, // instruction index to find signature
public_key_offset: u16, // offset to compressed public key of 33 bytes
public_key_instruction_index: u16, // instruction index to find public key
message_data_offset: u16, // offset to start of message data
message_data_size: u16, // size of message data
message_instruction_index: u16, // index of instruction data to get message data
}
所有签名都强制使用低 S 值,以避免意外的签名可变性。
Signature verification psuedocode
process_instruction() {
if data.len() < SIGNATURE_OFFSETS_START {
return Error
}
num_signatures = data[0] as usize
if num_signatures == 0 || num_signatures > 8 {
return Error
}
expected_data_size = num_signatures * SIGNATURE_OFFSETS_SERIALIZED_SIZE + SIGNATURE_OFFSETS_START
if data.len() < expected_data_size {
return Error
}
for i in 0..num_signatures {
offsets = parse_signature_offsets(data, i)
signature = get_data_slice(data, instruction_datas, offsets.signature_instruction_index, offsets.signature_offset, SIGNATURE_SERIALIZED_SIZE)
if s > half_curve_order {
return Error
}
pubkey = get_data_slice(data, instruction_datas, offsets.public_key_instruction_index, offsets.public_key_offset, COMPRESSED_PUBKEY_SERIALIZED_SIZE)
message = get_data_slice(data, instruction_datas, offsets.message_instruction_index, offsets.message_data_offset, offsets.message_data_size)
if !verify_signature(signature, pubkey, message) {
return Error
}
}
return Success
}

核心程序

以下列表中的程序提供了网络的核心功能。

程序程序 ID描述指令链接
System11111111111111111111111111111111创建新账户、分配账户数据、将账户分配给拥有程序、从 System Program 拥有的账户转移 lamports,并支付交易费用SystemInstruction
VoteVote111111111111111111111111111111111111111创建和管理跟踪验证者投票状态和奖励的账户VoteInstruction
StakeStake11111111111111111111111111111111111111创建和管理代表委托给验证者的权益和奖励的账户StakeInstruction
ConfigConfig1111111111111111111111111111111111111将配置数据添加到链中,后跟允许修改它的公钥列表。与其他程序不同,Config 程序未定义任何单独的指令。它只有一个隐式指令:“存储”。其指令数据是一组控制访问账户和存储在其中数据的密钥。ConfigInstruction
Compute BudgetComputeBudget111111111111111111111111111111设置交易的计算单元限制和价格,允许用户控制计算资源和优先级费用ComputeBudgetInstruction
Address Lookup TableAddressLookupTab1e1111111111111111111111111管理地址查找表,允许交易引用比交易账户列表中通常能容纳的更多账户ProgramInstruction
ZK ElGamal ProofZkE1Gama1Proof11111111111111111111111111111提供对 ElGamal 加密数据的零知识证明验证

Is this page helpful?

Table of Contents

Edit Page

管理者

©️ 2025 Solana 基金会版权所有
取得联系
程序 | Solana