这篇文章详细介绍了Solana的本地链上程序,包括其作用、系统程序、代币程序、质押程序等的具体功能和调用方式。同时探讨了跨程序调用的机制以及安全性考量,提供了程序ID一览和相关代码示例,是对Solana链上程序的深度解析。
Solana 有一些内置的(原生链上)程序(例如 system_program, spl_token, stake, vote, ed25519 等),提供基本的指令并且一般是可信的。
在这篇文章中,我们介绍这些程序的内部机制,并强调一些细节。
每个 Solana 程序(包括原生和用户部署的智能合约)都有一个唯一的 program_id,对应程序的公钥(pubkey)。
以下是原生 Solana 程序及其对应的 program_ids 列表
原生 Solana 程序 | 程序 ID |
---|---|
system_program | 11111111111111111111111111111111 |
stake | Stake11111111111111111111111111111111111111 |
vote | Vote111111111111111111111111111111111111111 |
config | Config1111111111111111111111111111111111111 |
BPFLoaderUpgradeab1e | BPFLoaderUpgradeab1e11111111111111111111111 |
Ed25519 | Ed25519SigVerify111111111111111111111111111 |
Secp256k1 | KeccakSecp256k11111111111111111111111111111 |
此外,spl_token 和 spl_associated_token_account 也是官方 Solana 程序,使用频率较高,因此我们也考虑它们:
官方 Solana 程序 | 程序 ID |
---|---|
spl_token | TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA |
spl_associated_token_account | ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL |
当一个程序调用另一个程序时,被调用程序的 program_id 将会通过以下两种函数之一传递给调用:
solana_program::program::invoke
solana_program::program::invoke_signed
注意:在内部,invoke 调用 invoke_signed,但没有 signer_seeds。
program_id 是 instruction 的第一个参数,如下所定义:
在 invoke 和 invoke_signed 内,首先进行 RefCell 检查,以确保账户 RefCells 与请求一致:
由于嵌套循环,RefCell 检查可能会消耗计算单元。
为了避免这个开销,dapps 可能选择使用 invoke_unchecked 和 invoke_signed_unchecked,即 invoke 和 invoke_signed 的未检查版本,不会检查 RefCells。然而,请自行承担使用 invoke_unchecked 和 invoke_signed_unchecked 的风险:仅在确认指令中使用的账户与 account_infos 中的账户一致时使用。
在 Solana 中,每个账户,包括 Solana 程序都有一个所有者。当在跨程序调用中调用 Solana 程序时,其所有者用于处理指令(invoke 或 invoke_signed)。对于 Solana 程序,它们的所有者可以是 native loader 或 BPF 加载器。
native loader(NativeLoader1111111111111111111111111111111)是一个特殊程序,是大多数原生 Solana 程序的所有者(那些 program_ids 以 111111111111111111111111111 结尾)。
当在跨程序调用中调用原生 Solana 程序时,使用原生加载器将原生程序加载到 Solana 运行时并处理指令。
原生加载器还是三个 BPF 加载器的所有者:
大多数 Solana 智能合约使用可升级 BPF 加载器来部署程序,因此它们的所有者是 BPFLoaderUpgradeab1e,并且可以进行升级(由在程序部署时设置的升级权限控制)。
一些 Solana 程序是不可变的,因为它们是由 BPFLoader2 或 BPFLoader 加载的。
例如,SPL Token spl_token 和关联代币 spl_associated_token_account 程序由 BPFLoader2 加载,并且它们是不可变的。
系统程序 可能是被调用最频繁的程序,通常使用以下两条指令调用:
系统程序提供多个重要功能:
系统程序是所有钱包账户的 所有者。
注意:只有账户的所有者才能对账户进行写入访问。如果一个账户没有被程序拥有,程序只能读取其数据并为账户充值(但不能从账户中扣除)。
当账户通过 create_account 创建时,系统程序也是该账户的 默认所有者。然后允许转移 lamports,并且重要的是 分配 账户所有权,即将所有者更改为不同的程序_id。
SPL Token 程序 提供用于创建和管理代币的功能(包括可替代代币和不可替代代币,即 NFTs)。
spl_token 通常用于创建新代币、铸造、销毁和分发给用户。以下指令在 Solana 智能合约中经常使用:
关联代币程序 允许用户为他们拥有的每个代币创建主代币账户。内部将用户的钱包地址映射到每个代币铸造的唯一关联代币账户。
具体来说,要为给定的钱包地址和代币铸造创建关联代币账户: create_associated_token_account
Stake 程序 用于创建和管理表示验证者或其委托人权益和奖励的账户。
以下指令经常使用:
投票程序 用于创建和管理跟踪验证者投票状态和奖励的账户。
配置程序 用于将配置数据添加到链上,包括允许修改数据的公钥列表。
投票和配置主要由 Solana 验证者使用,因此我们在此省略细节。
Ed25519 签名验证程序 接收一个 ed25519 签名、公钥和消息,并用于验证该消息是否由(相应的私钥)签名。
默认情况下,使用所有可用 CPU 核心并行验证签名。当可用性能库时,签名验证会转移到 GPU。
可以验证多个签名。如果任意签名验证失败,将返回错误。
Secp256k1 恢复程序 用于从已签名消息中恢复 Secp256k1 公钥(ecrecover)。这是为了支持以太坊 / Solana 桥接。
Secp256k1 是流行区块链(例如,比特币、以太坊)用于实现公钥密码学的椭圆曲线名称。该曲线上的所有点都是有效的公钥。
Ed25519 和 Secp256k1 程序在 Solana 中预编译以最大化性能。
应该注意,尽管这些原生程序通常是可信赖的(且大部分是稳定的),但为了确保安全,了解其假设并以预期的方式使用指令是很重要的。
我们使用 SPL Token 程序的安全案例来说明这一点。
在 spl-token v3.1.1(于 2021 年 5 月 18 日发布)之前,代币指令代码存在一个漏洞,允许调用任意程序(而不是实际的 spl-token 程序)。修复在 这个提交 中。
修复在所有代币指令函数中添加了 check_program_account,以确保用户提供的 token_program_id 与 spl-token program_id TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA 相同。
check_program_account 函数如下所示:
该漏洞的根本原因在于对 spl-token 程序预期用法与潜在用法之间存在不一致的假设。
因此,为了避免一般性这样的漏洞(从而防御攻击),重要的是
检查这些原生程序的预期用法
添加必要的用户检查
始终使用修复过的版本(例如,spl-token v3.1.1 及以上)。
sec3 是一家安全研究公司,为数百万用户的 Solana 项目提供保障。sec3 的 Launch Audit 是一项严格的、由研究人员主导的代码检查,调查并认证主网级智能合约;sec3 的持续审计软件平台 X-ray 与 GitHub 集成,逐步扫描拉取请求,帮助项目在部署前巩固代码;而 sec3 的后部署安全解决方案 WatchTower 确保资金安全。sec3 正在构建基于技术的可扩展解决方案,以确保 Web3 项目在扩展时保持安全。
- 原文链接: sec3.dev/blog/solana-int...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!