RGB 智能合约深度探索:从手写 AluVM 汇编到理解 Contractum 演进
本文定位:学习性验证记录,通过亲手编写 v0.11 和 v0.12 两代 RGB 合约代码,深入理解客户端验证、AluVM 虚拟机、以及未来 Contractum 高级语言的演进逻辑。适合 RGB 开发者、生态合作方、协议研究者阅读。
声明与立场
基于「乐于分享」的价值观,我们把在 RGB 智能合约 上的探索与实践记录下来,供社区参考。
郑重声明:
本文所指的 RGB v0.12 与 早期的 v0.11,均由 RGB Consortium 与 LNP/BP 标准协会 开发。
本文 v0.11 侧统一锁定 RGB-WG 官方 v0.11.0-beta.9。对其它 tag / 发布线:
- 社区 不应 将本文作为不同版本的争议引用来源,也 无意 引起纷争
- 我们将两个版本进行对比,是希望学习者能通过 代码具体实现的变化,更好理解 RGB 协议如何迭代演进
为什么要做这次探索?
作为 RGB 生态的开发者和研究者,我们发现社区普遍存在以下困惑:
- RGB 智能合约到底是用什么语言写的?
- v0.11 和 v0.12 智能合约有什么本质差异?
- 未来的 Contractum 智能合约语言 是什么?
- 客户端验证的底层机制是怎样的?
与其停留在文档阅读和理论推导,我们决定亲手编写两个版本的 RGB 合约的完整代码,跑通从发行到转账的全流程,用实践验证来回答这些问题。
验证范围说明
我们选择 RGB20 同质化代币 作为对照案例,在 v0.11 和 v0.12 上分别手写最小实现
为什么选择 RGB20?
- RGB20(资产守恒:转账前后代币总量相等)是两个版本官方都有对应实现的案例(v0.11 的 NIA、v0.12 的 RGB20-FNA),对照更直接
- NIA (Non-Inflatable Asset):v0.11 官方参考实现,不可增发资产
- RGB20-FNA (Fungible Non-inflatable Asset):v0.12 官方参考实现,同质化不可增发资产
- 「转账守恒」这个不变量足够简单,能在两套完全不同的验证机制上分别真实实现
第一部分:实践验证过程
我们详细记录亲手编写两代合约的完整过程,这是本文说服力的来源。
v0.11 实践:RGB-WG 官方 v0.11.0-beta.9
验证环境
| crate | 版本 | 来源 |
|---|---|---|
rgb-core | 0.11.0-beta.9 | github.com/RGB-WG/rgb-core |
rgb-std | 0.11.0-beta.9 | github.com/RGB-WG/rgb-std |
rgb-interfaces | 0.11.0-beta.9 | github.com/RGB-WG/rgb-interfaces |
aluvm | 0.11.0-beta.9 | RGB-WG 生态同期发布 |
代码位置:v0.11-rgb20/(rgb-smart-contract-demo)
合约结构:Schema + Iface + IfaceImpl
v0.11 的合约由三个对象共同定义:
Schema "SimpleRGB20" Iface "SimpleRGB20Iface"
├─ global_types ├─ global_state: spec/terms/issuedSupply
│ ├─ spec (AssetSpec, once) ├─ assignments: assetOwner (Amount)
│ ├─ terms (ContractTerms) ├─ genesis (globals/assignments occurrences)
│ └─ issuedSupply (Amount, once) └─ transitions: transfer
├─ owned_types
│ └─ assetOwner (Fungible<u64>) IfaceImpl (binds Schema <-> Iface)
├─ genesis ├─ GS_NOMINAL <-> "spec"
│ ├─ globals/assignments occurrences ├─ GS_TERMS <-> "terms"
│ └─ validator -> LibSite(FN_GENESIS_OFFSET) ├─ GS_ISSUED_SUPPLY <-> "issuedSupply"
└─ transitions ├─ OS_ASSET <-> "assetOwner"
└─ transfer (TS_TRANSFER=10000) └─ TS_TRANSFER <-> "transfer"
├─ inputs/assignments occurrences
└─ validator -> LibSite(FN_TRANSFER_OFFSET=0)核心代码(GitHub: v0.11-rgb20/src/main.rs):
// Schema:共识条款
fn schema() -> Schema {
let types = types();
let lib = rgb20_lib();
let lib_id = lib.id();
Schema {
name: tn!("SimpleRGB20"),
global_types: tiny_bmap! {
GS_NOMINAL => GlobalStateSchema::once(types.get("RGBContract.AssetSpec")),
GS_TERMS => GlobalStateSchema::once(types.get("RGBContract.ContractTerms")),
GS_ISSUED_SUPPLY => GlobalStateSchema::once(types.get("RGBContract.Amount")),
},
owned_types: tiny_bmap! {
OS_ASSET => OwnedStateSchema::Fungible(FungibleType::Unsigned64Bit),
},
genesis: GenesisSchema {
validator: Some(LibSite::with(FN_GENESIS_OFFSET, lib_id)),
..
},
transitions: tiny_bmap! {
TS_TRANSFER => TransitionSchema {
validator: Some(LibSite::with(FN_TRANSFER_OFFSET, lib_id)),
..
}
},
..
}
}
// Iface:人类可读界面
fn iface() -> Iface {
Iface {
name: tn!("SimpleRGB20Iface"),
global_state: tiny_bmap! {
fname!("spec") => GlobalIface::required(types.get("RGBContract.AssetSpec")),
fname!("issuedSupply") => GlobalIface::required(types.get("RGBContract.Amount")),
},
assignments: tiny_bmap! {
fname!("assetOwner") => AssignIface::private(OwnedIface::Amount, Req::OneOrMore),
},
transitions: tiny_bmap! {
fname!("transfer") => TransitionIface { .. },
},
..
}
}
// IfaceImpl:绑定 Schema 和 Iface
fn iimpl(schema: &Schema, iface: &Iface) -> IfaceImpl {
IfaceImpl {
schema_id: schema.schema_id(),
iface_id: iface.iface_id(),
global_state: tiny_bset! { NamedField::with(GS_NOMINAL, fname!("spec")) },
assignments: tiny_bset! { NamedField::with(OS_ASSET, fname!("assetOwner")) },
transitions: tiny_bset! { NamedField::with(TS_TRANSFER, fname!("transfer")) },
..
}
}AluVM 验证脚本:pcvs/pcas 专用指令
fn rgb20_lib() -> Lib {
let code = rgbasm! {
// SUBROUTINE Transfer validation
put a8[0],ERRNO_NON_EQUAL_IN_OUT;
// Checking that the sum of Pedersen commitments in inputs equals the sum in outputs.
pcvs OS_ASSET;
test;
ret;
// SUBROUTINE Genesis validation
put a8[0],ERRNO_ISSUED_MISMATCH;
put a8[1],0;
put a16[0],0;
ldg GS_ISSUED_SUPPLY,a8[1],s16[0];
extr s16[0],a64[0],a16[0];
pcas OS_ASSET;
test;
ret;
};
Lib::assemble::<Instr<RgbIsa<MemContract>>>(&code)
}技术细节:pcvs/pcas 验证的是盲化后的 Pedersen 承诺之和,不是明文金额。这是 RGB 保密交易机制的一部分:每笔 RevealedValue 都带一个随机 BlindingFactor,链上只比较承诺的代数和是否相等。
验证流程
步骤 1:构造合约实例
let contract = ContractBuilder::with(
Identity::default(),
iface.clone(),
schema.clone(),
iimpl.clone(),
types.clone(),
scripts.clone(),
)
.add_global_state("spec", AssetSpec::new("DEMO", "Demo RGB20 v0.11 Token", Precision::CentiMicro))
.add_global_state("terms", ContractTerms { text: Default::default(), media: None })
.add_global_state("issuedSupply", Amount::from(100_000u64))
.add_fungible_state("assetOwner", alice_seal, 70_000u64)
.add_fungible_state("assetOwner", bob_seal, 30_000u64)
.issue_contract()
.expect("contract doesn't fit schema requirements");步骤 2:有效转账(Alice 70000 → Bob +10000, Alice-change 60000)
let valid = run_transfer(
&schema, &iface, &iimpl, &types, &scripts,
70_000u64, // 输入
&[10_000u64, 60_000u64], // 输出
);结果:✅ 通过验证(pcvs 指令确认承诺之和相等)
步骤 3:无效转账(spend 10000 但 output 999999)
let invalid = run_transfer(
&schema, &iface, &iimpl, &types, &scripts,
10_000u64, // 输入
&[999_999u64], // 输出(故意不守恒)
);结果:❌ 正确拒绝
ScriptFailure(OpId(...), Some(0), None)错误码 0 对应我们定义的 ERRNO_NON_EQUAL_IN_OUT,说明 pcvs 指令检测到承诺之和不相等。
v0.11 验证结论
✅ Schema 就是智能合约:通过手写 Schema + Iface + IfaceImpl,我们从零构造了一个可用的 RGB20 代币合约
✅ pcvs 指令能正确拒绝不守恒转账:验证了客户端验证的正确性
✅ Pedersen 承诺保密金额:协议层原生支持盲化交易
v0.12 实践:SONIC/UltraSONIC
验证环境
| crate | 版本 |
|---|---|
aluvm | 0.12.0 |
ultrasonic | 0.12.0 |
sonic-api | 0.12.0 |
hypersonic | 0.12.0 |
sonic-persist-fs | 0.12.0 |
代码位置:v0.12-rgb20/(rgb-smart-contract-demo)
合约结构:Codex + Api + Semantics
v0.12 把"条款"和"界面"彻底分离:
Codex(条款) Api(界面)
├─ verifiers: call_id → LibSite ├─ global: _ticker/_name/_precision/_issuedSupply
└─ 不含状态定义 ├─ owned: balance (Fungible)
├─ aggregators: ticker/issuedSupply
└─ verifiers: issue/transfer → call_id核心代码(GitHub: v0.12-rgb20/src/main.rs):
// Codex:只有 call_id -> LibSite 的映射
fn codex() -> Codex {
let compiled = libs::build();
let issue_site = compiled.routine(libs::ROUTINE_ISSUE);
let transfer_site = compiled.routine(libs::ROUTINE_TRANSFER);
Codex {
name: tiny_s!("SimpleRGB20"),
verifiers: tiny_bmap! {
CALL_ISSUE => issue_site,
CALL_TRANSFER => transfer_site,
},
..
}
}
// Api:状态定义拆到这里
fn api() -> Api {
let types = stl::Rgb20Types::new();
let codex = codex();
Api {
codex_id: codex.codex_id(),
global: tiny_bmap! {
vname!("_ticker") => GlobalApi {
sem_id: types.get("RGB20.Ticker"),
..
},
vname!("_issuedSupply") => GlobalApi {
sem_id: types.get("RGB20.Amount"),
..
},
},
owned: tiny_bmap! {
vname!("balance") => OwnedApi {
sem_id: types.get("RGB20.Amount"),
arithmetics: StateArithm::Fungible,
..
}
},
verifiers: tiny_bmap! {
vname!("issue") => CALL_ISSUE,
vname!("transfer") => CALL_TRANSFER,
},
..
}
}AluVM 验证脚本:手写通用指令循环
完整代码(GitHub: v0.12-rgb20/src/libs.rs):
pub fn build() -> CompiledLib {
let code: Vec<Instr<LibId>> = uasm! {
// ---- routine TRANSFER: transfer verifier ------------------------------------------
// Invariant: sum(destructible_in) == sum(destructible_out)
routine TRANSFER:
put E2, 0; // 1. 初始化输入累加器
put E3, 0; // 2. 初始化输出累加器
rsti destructible; // 3. 重置输入迭代器
rsto destructible; // 4. 重置输出迭代器
loop LOOP_IN: // 5. 开始遍历输入
cknxi destructible; // 6. 检查是否还有下一个输入
jif CO, EXIT_IN; // 7. 如果没有了,跳到 EXIT_IN
ldi destructible; // 8. 加载当前输入的值到 EB 寄存器
add E2, EB; // 9. 累加到 E2(输入总和)
jmp LOOP_IN; // 10. 跳回循环开头
label EXIT_IN: // 11. 输入遍历结束
loop LOOP_OUT: // 12. 开始遍历输出
cknxo destructible; // 13. 检查是否还有下一个输出
jif CO, EXIT_OUT; // 14. 如果没有了,跳到 EXIT_OUT
ldo destructible; // 15. 加载当前输出的值到 EB 寄存器
add E3, EB; // 16. 累加到 E3(输出总和)
jmp LOOP_OUT; // 17. 跳回循环开头
label EXIT_OUT: // 18. 输出遍历结束
eq E2, E3; // 19. 比较 E2(输入和)== E3(输出和)
chk CO; // 20. 检查比较结果,不相等则中止验证
stop; // 21. 验证结束
};
CompiledLib::compile(code, &[])
}代码解析:这 26 行汇编在做和 v0.11 的 pcvs 完全相同的事情(遍历输入、累加、遍历输出、累加、比较),只是:
- v0.11:协议层把这些步骤封装在
pcvs指令里 - v0.12:开发者自己用通用指令手写这些步骤
验证流程
步骤 1:构造合约实例
let issuer = Issuer::new(codex, semantics).expect("invalid codex/semantics pair");
issuer.save(&issuer_path).expect("unable to save issuer");
let articles = issuer
.start_issue_testnet("issue", Consensus::None)
.append("_ticker", svstr("DEMO"), None)
.append("_name", svstr("Demo RGB20 v0.12 Token"), None)
.append("_precision", svnum!(2u8), None)
.append("_issuedSupply", svnum!(100_000u64), None)
.assign("balance", alice_auth, svnum!(70_000u64), None)
.assign("balance", bob_auth, svnum!(30_000u64), None)
.finish("SimpleRGB20Demo", 1732529307);
let mut ledger = LedgerDir::new(articles, contract_path.to_path_buf());步骤 2:有效转账(Alice 70000 → Bob +10000, Alice-change 60000)
let alice_cell = ledger.state().addr(alice_auth);
let transfer_opid = ledger
.start_deed("transfer")
.using(alice_cell)
.assign("balance", bob_auth, svnum!(10_000u64), None)
.assign("balance", carol_auth, svnum!(60_000u64), None)
.commit()
.expect("valid transfer must succeed");结果:✅ 通过验证(手写循环确认输入和 == 输出和)
步骤 3:无效转账(spend 10000 but output 999999)
let bob_received_cell = CellAddr::new(transfer_opid, 0);
let result = ledger
.start_deed("transfer")
.using(bob_received_cell)
.assign("balance", mallory_auth, svnum!(999_999u64), None)
.commit();结果:❌ 正确拒绝
Error: ScriptUnspecified由于本验证未在脚本中设置错误码分支(没有写 E1 寄存器),返回无错误码的 ScriptUnspecified,但验证确实失败了。
v0.12 验证结论
✅ 图灵完备:用 jif/jmp 实现真实循环(循环次数 = cell 数量,不是固定展开)
✅ 26 行汇编完成守恒检查:和 v0.11 的 pcvs 功能等价
✅ 正确拒绝不守恒转账:客户端验证机制工作正常
两代技术核心代码对比
这是本文最重要的对比,保留在 blog 中供读者参考。
守恒验证对比
v0.11(4 行):
let code = rgbasm! {
put a8[0],ERRNO_NON_EQUAL_IN_OUT;
pcvs OS_ASSET; // ← 协议内置专用指令
test;
ret;
};v0.12(26 行):
let code = uasm! {
routine TRANSFER:
put E2, 0; put E3, 0;
rsti destructible; rsto destructible;
loop LOOP_IN:
cknxi destructible; jif CO, EXIT_IN;
ldi destructible; add E2, EB; jmp LOOP_IN;
label EXIT_IN:
loop LOOP_OUT:
cknxo destructible; jif CO, EXIT_OUT;
ldo destructible; add E3, EB; jmp LOOP_OUT;
label EXIT_OUT:
eq E2, E3; chk CO; stop;
};Contractum(1 行,未来):
verify {
inputs.balance.sum() == outputs.balance.sum()
}状态定义对比
v0.11(Schema + Iface + IfaceImpl 三层):
// Schema: 数值 ID
global_types: tiny_bmap! {
GS_NOMINAL => GlobalStateSchema::once(types.get("RGBContract.AssetSpec")),
},
// Iface: 字段名
global_state: tiny_bmap! {
fname!("spec") => GlobalIface::required(types.get("RGBContract.AssetSpec")),
},
// IfaceImpl: 绑定表
global_state: tiny_bset! {
NamedField::with(GS_NOMINAL, fname!("spec")),
},v0.12(Codex + Api 两层):
// Codex: 只有路由
verifiers: tiny_bmap! {
CALL_TRANSFER => transfer_site,
},
// Api: 状态定义
global: tiny_bmap! {
vname!("_ticker") => GlobalApi { sem_id: types.get("RGB20.Ticker"), .. },
},Contractum(一层,未来):
global _ticker: String;
owned balance: Zk64;第二部分:核心发现
发现 1:RGB 合约的验证逻辑是 AluVM 汇编,不是 Rust
这是我们验证过程中最重要的发现,也是最容易被误解的地方。
常见误解:看到源码文件是 .rs 后缀,就以为 RGB 合约是用 Rust 写的。
真相:RGB 合约的验证逻辑是 AluVM 汇编字节码,Rust 只是宿主语言和构建工具。
参考具体代码: https://github.com/rgbstashlabs/rgb-smart-contract-demo
v0.11 示例(RGB-WG 官方 v0.11.0-beta.9)
fn rgb20_lib() -> Lib {
let code = rgbasm! {
// SUBROUTINE Transfer validation
put a8[0],ERRNO_NON_EQUAL_IN_OUT;
// Checking that the sum of Pedersen commitments in inputs equals the sum in outputs.
pcvs OS_ASSET; // ← 这是 AluVM 指令,不是 Rust 函数调用
test;
ret;
// SUBROUTINE Genesis validation
put a8[0],ERRNO_ISSUED_MISMATCH;
put a8[1],0;
put a16[0],0;
ldg GS_ISSUED_SUPPLY,a8[1],s16[0];
extr s16[0],a64[0],a16[0];
pcas OS_ASSET;
test;
ret;
};
Lib::assemble::<Instr<RgbIsa<MemContract>>>(&code)
}代码解读:
rgbasm!是一个编译期宏,把put/pcvs/test/ret这些看起来像标识符的东西展开成Vec<Instr<RgbIsa>>(AluVM 指令序列)Lib::assemble把指令序列编译成 AluVM 字节码库- 验证时,
Schema::validate_state调用 AluVM 虚拟机执行这段字节码,不是执行 Rust 函数
Rust 在这里的作用:只是让你用 Rust 的编译器写汇编(通过宏的语法糖),编译产物是字节码。
v0.12 示例(SONIC/UltraSONIC)
pub fn build() -> CompiledLib {
let code: Vec<Instr<LibId>> = uasm! {
routine TRANSFER:
put E2, 0;
put E3, 0;
rsti destructible;
rsto destructible;
loop LOOP_IN:
cknxi destructible; // ← 检查是否有下一个输入
jif CO, EXIT_IN; // ← 条件跳转(汇编指令)
ldi destructible; // ← 加载输入值
add E2, EB; // ← 累加到寄存器 E2
jmp LOOP_IN; // ← 无条件跳转回循环开头
label EXIT_IN:
loop LOOP_OUT:
cknxo destructible;
jif CO, EXIT_OUT;
ldo destructible;
add E3, EB;
jmp LOOP_OUT;
label EXIT_OUT:
eq E2, E3; // ← 比较两个寄存器
chk CO; // ← 检查比较结果,失败则中止
stop;
};
CompiledLib::compile(code, &[])
}代码解读:
uasm!同样是编译期宏(来自ultrasoniccrate),把cknxi/jif/jmp/add/eq展开成Vec<Instr<LibId>>- 你看到的
loop/label不是 Rust 的控制流,是汇编里的标签(goto 目标) jif/jmp通过这些标签的索引实现跳转
类比:
// 这是 Rust 循环(for/while)
for i in 0..count {
sum += items[i];
}
// 上面 uasm! 里的是汇编循环(jmp 回标签)
LOOP_IN:
cknxi destructible;
jif CO, EXIT_IN; // 如果没有下一个输入,跳到 EXIT_IN
ldi destructible;
add E2, EB;
jmp LOOP_IN; // 跳回 LOOP_IN 标签
EXIT_IN:两者表面相似,但前者编译成 Rust 本地机器码,后者编译成 AluVM 字节码指令。
关键结论:RGB 合约的验证逻辑必须写成 AluVM 汇编,因为客户端验证要求"发送方和接收方各自独立执行同一段确定性字节码,结果必须一致"。如果验证逻辑是本地机器码(x86/ARM),不同平台的行为可能不一致。
发现 2:为什么 v0.11 求和只需要 1 行,v0.12 却要 26 行?
这是最让人困惑的问题:表面上看,v0.12 像是"退步"了。
v0.11 的守恒验证:1 行指令
pcvs OS_ASSET; // ← Pedersen 承诺求和验证(一条指令完成)v0.12 的守恒验证:26 行循环
routine TRANSFER:
put E2, 0; put E3, 0;
rsti destructible; rsto destructible;
loop LOOP_IN:
cknxi destructible; jif CO, EXIT_IN;
ldi destructible; add E2, EB; jmp LOOP_IN;
label EXIT_IN:
loop LOOP_OUT:
cknxo destructible; jif CO, EXIT_OUT;
ldo destructible; add E3, EB; jmp LOOP_OUT;
label EXIT_OUT:
eq E2, E3; chk CO; stop;真相:专用指令 vs 通用指令
v0.11 的 pcvs 不是"普通汇编指令",而是 RGB 协议层面专门为资产验证设计的高级指令,类似:
- GPU 的
matrix_multiply()专用指令(一条指令完成复杂矩阵运算) - SQL 的
SUM(balance)聚合函数(一条语句完成求和)
pcvs 内部做了什么(协议层实现):
- 遍历所有
OS_ASSET类型的输入 UTXO - 提取每个输入的 Pedersen 承诺值
- 累加所有输入的承诺(椭圆曲线点加法)
- 遍历所有
OS_ASSET类型的输出 UTXO - 累加所有输出的承诺
- 比较两个承诺点是否相等
这 6 步逻辑被封装在 pcvs 指令里,所以看起来只有 1 行,实际是协议层帮你做了循环。
v0.12 的 26 行是用"通用指令"手写这 6 步:
cknxi(检查下一个输入)ldi(加载输入值)add(累加)jif(条件跳转)jmp(无条件跳转)
这些指令类似 CPU 的 mov/add/cmp/jmp,需要手写循环。
为什么要从 1 行变 26 行?为了灵活性
重要更新(2026-08-12 验证):经过深入源码分析,我们发现 v0.11 理论上可以但实际上无法像 v0.12 那样手写循环。
v0.11 的 pcvs | v0.12 的 26 行循环 | v0.11 能手写循环吗? |
|---|---|---|
| 只能做"无条件求和比较" | 可以在循环里加任何逻辑 | ❌ 汇编语法糖不支持 |
| 无法实现"VIP 用户转账限额" | 可以在循环里加 if (amount > 1000) check_VIP() | ❌ 缺少迭代器模式 |
| 无法实现"多签 3/5 授权" | 可以读取全局状态检查签名数 | ⚠️ 可读全局但无法遍历输入 |
| 无法实现"AMM 滑点计算" | 可以读取牌价、计算滑点、验证输出 | ❌ 无法自定义守恒逻辑 |
类比总结:
| 任务 | v0.11 做法 | v0.12 做法 | 类比 |
|---|---|---|---|
| 矩阵乘法 | matrix_multiply(A, B) | 三层 for 循环手写 | GPU 专用指令 vs CPU 通用指令 |
| 数据库求和 | SELECT SUM(balance) | for (row in table) { sum += row.balance; } | SQL 聚合函数 vs 程序循环 |
| 资产守恒 | pcvs OS_ASSET; | 26 行手写循环 | RGB ISA 专用指令 vs AluVM 通用指令 |
核心洞察:
- v0.11 → v0.12:从专用硬件到通用处理器(牺牲简洁性换取灵活性)
- v0.12 的 26 行不是"退步",而是为了支持任意复杂逻辑而必须付出的代价
- v0.11 的根本限制:虽然底层 AluVM 支持
jmp/jif和算术指令,但缺少迭代器模式的状态访问指令(cknxi/ldi),且rgbasm!宏未暴露cnp/cns/ldf助记符,导致无法实际编写自定义守恒逻辑
发现 3:v0.11 能否写自定义合约?技术验证
验证问题:v0.11 是否能像 v0.12 那样手写守恒循环?
底层能力对比
| 指令类型 | v0.11 (AluVM 0.11 + RgbIsa) | v0.12 (AluVM 0.12 + UltraSONIC) |
|---|---|---|
| 控制流 | ✅ jmp/jif/routine/call | ✅ jmp/jif/call |
| 算术 | ✅ add/sub/mul/div/eq | ✅ add/sub/mul/div/eq |
| 状态计数 | ⚠️ CnP/CnS (底层有,宏无) | ✅ 不需要(用迭代器) |
| 状态加载 | ⚠️ LdF(type, idx, val) (底层有,宏无) | ✅ ldi/ldo (自动迭代) |
| 迭代器 | ❌ 无 | ✅ rsti/rsto/cknxi/cknxo |
关键差异:状态访问模式
v0.11 的索引访问模式:
// 理论上可行,但 rgbasm! 不支持
cnp OS_ASSET, a16[0]; // 获取输入数量 → a16[0]
put a16[1], 0; // 索引初始化
loop LOOP:
lt.u a16[1], a16[0]; // 索引 < 数量?
jif EXIT;
ldf OS_ASSET, a16[1], a64[2]; // ❌ rgbasm! 缺少 ldf 助记符
add.uw a64[2], a64[0]; // 累加
inc a16[1]; // 索引++
jmp LOOP;v0.12 的迭代器模式:
// uasm! 原生支持
rsti destructible; // 重置输入迭代器
loop LOOP:
cknxi destructible; // 检查是否有下一个
jif CO, EXIT;
ldi destructible; // 自动加载 + 移动迭代器
add E2, EB; // 累加
jmp LOOP;rgbasm! 宏的限制
v0.11 的 rgbasm! 仅暴露这些 RGB 扩展指令:
pcvs // Pedersen commitment verify sum(专用守恒指令)
pcas // Pedersen commitment against sum
pcps // Pedersen commitment previous sum
ldg // Load global state
ldp // Load previous structured state
lds // Load state structured
cng // Count global
cnc // Count contract global
ldm // Load metadata缺失的关键指令:
- ❌
cnp/cns(计数输入/输出数量) - ❌
ldf(按索引加载 fungible 值)
虽然底层 ContractOp 枚举包含 CnP/CnS/LdF,但 isa_instr! 宏未提供助记符,开发者需要手动构造 Instr<RgbIsa<_>> 枚举值,这与"汇编语言"的定位矛盾。
验证结论
| 维度 | v0.11 | v0.12 |
|---|---|---|
| 控制流图灵完备性 | ✅ 支持 jmp/jif/loop/label | ✅ 支持 |
| 算术能力 | ✅ 完整的加减乘除比较 | ✅ 完整 |
| 状态遍历能力 | ❌ 无迭代器,需手动索引 | ✅ 迭代器模式 |
| 汇编宏支持 | ⚠️ 核心指令缺失助记符 | ✅ 完整暴露 |
| 实际可写性 | ❌ 只能用专用指令 pcvs | ✅ 可手写任意逻辑 |
结论:
- 底层能力:v0.11 的 AluVM + RgbIsa 包含图灵完备的控制流和算术指令
- 关键缺失:缺少迭代器模式(
cknxi/ldi)导致状态遍历需要手动索引管理,且rgbasm!未暴露必要的计数/加载指令助记符 - 实际结果:v0.11 开发者无法像 v0.12 那样手写守恒循环,只能依赖
pcvs/pcas等专用指令
这解释了为什么 v0.11 官方示例都使用 pcvs,而不是手写循环——不是不想,而是工具链不支持。
发现 4:v0.11 和 v0.12 是两条并行路线,Contractum 选择了 v0.12
这是对生态合作方和开发者最重要的结论。
关键事实:
- v0.11 和 v0.12 不是版本升级关系,而是两条并行存在的技术栈
- Contractum 编译器生成的是 v0.12 风格的通用指令(
cknxi/ldi/jif/jmp),而不是 v0.11 的专用指令(pcvs) - v0.11 无法直接"升级"到 Contractum,因为底层指令集不同
为什么 Contractum 选择 v0.12 而不是 v0.11?
| 需求 | v0.11 能做吗? | v0.12 能做吗? | Contractum 需要吗? |
|---|---|---|---|
| 简单守恒检查 | ✅ pcvs 一行搞定 | ✅ 26 行循环 | ✅ 必须支持 |
| 条件转账(VIP 限额) | ❌ pcvs 无法表达条件 | ✅ 循环里加 if 逻辑 | ✅ 必须支持 |
| 多签授权(3/5) | ❌ pcvs 无法读全局状态 | ✅ 读 immutable_in | ✅ 必须支持 |
| AMM 滑点计算 | ❌ pcvs 无法读牌价 | ✅ 读全局+计算+验证 | ✅ 必须支持 |
| DAO 治理 | ❌ pcvs 只看输入输出 | ✅ 读权限+投票+执行 | ✅ 必须支持 |
结论:Contractum 的目标是"支持任意复杂的智能合约",所以必须基于 v0.12 的通用指令模型,v0.11 的专用指令模型太受限。
Contractum 示例(未来,开发中)
schema FungibleAsset {
global _issuedSupply: Amount;
owned balance: Zk64;
transition transfer {
owned balance-;
owned balance+;
verify {
inputs.balance.sum() == outputs.balance.sum()
}
}
}这 1 行声明式代码会被编译器自动生成 v0.12 的 26 行循环汇编,开发者不用手写。
三代技术对比
| 维度 | v0.11(第一代) | v0.12(第二代) | Contractum(第三代) |
|---|---|---|---|
| 时间 | 2023-2024 | 2024-2026 | 开发中(未来) |
| 核心库 | rgb-core 0.11 | SONIC/UltraSONIC | Contractum 编译器 |
| 开发语言 | AluVM 汇编(rgbasm!) | AluVM 汇编(uasm!) | Contractum 高级语言 |
| 代码量(RGB20 守恒) | 4 行(pcvs 专用指令) | 26 行(手写循环) | ~5 行(.sum() 聚合) |
| 图灵完备性 | 有限(依赖预定义指令) | 是(手写循环+跳转) | 是(编译器生成循环) |
| 金额保密性 | Pedersen 承诺(盲化) | 明文(本次验证) | Zk64 类型(预期支持) |
| 适用场景 | 简单代币(RGB20/RGB21) | 复杂合约(AMM/DAO) | 所有 RGB 合约 |
| 底层虚拟机 | AluVM + RGB ISA | AluVM + UltraSONIC | AluVM(编译目标) |
演进逻辑:
- v0.11 → v0.12:从专用指令到通用寄存器机,牺牲简洁性换取灵活性
- v0.12 → Contractum:从手写汇编到高级语言,用编译器复杂度换取开发体验
第三部分:给开发者和生态合作方的提示
对 RGB 开发者
- 理解底层再用高级工具:虽然未来有 Contractum,但理解 AluVM 汇编有助于调试和优化
- v0.11 和 v0.12 特征:
- 简单代币 → v0.11 的
pcvs足够(4 行代码) - 复杂逻辑(AMM/DAO/条件转账)→ 必须用 v0.12
- 简单代币 → v0.11 的
对协议研究者
- 客户端验证的确定性来源:AluVM 字节码跨平台执行结果一致
- 图灵完备性的实证:v0.12 的
jif/jmp循环是真实的图灵完备实现 - 保密交易的两种路线:
- v0.11:Pedersen 承诺(协议层原生支持)
- v0.12:本次验证用明文,但预期 Contractum 的
Zk64类型会支持
我们验证了什么
✅ v0.11 的 pcvs 指令能正确拒绝不守恒转账
✅ v0.12 的手写循环实现了图灵完备的验证逻辑
✅ 对比了两代技术的实际差异(代码量、灵活性、隐私性)
✅ 澄清了 Rust 和汇编的分层关系(验证逻辑是汇编,Rust 是工具)
参考资料
官方文档
- RGB 官网:https://rgb.tech/
- RGB FAQ:https://www.rgbfaq.com/
- Contractum 官网:https://www.contractum.org/
代码仓库
- 本文代码:https://github.com/rgbstashlabs/rgb-smart-contract-demo
- RGB-WG/rgb:https://github.com/RGB-WG/rgb
- RGB-WG/contractum-lang:https://github.com/RGB-WG/contractum-lang
- AluVM 虚拟机:https://github.com/AluVM
学术论文
组织仓库
- LNP/BP Standards Association:https://github.com/LNP-BP
- RGB-WG:https://github.com/RGB-WG
致谢
感谢 RGB Consortium、Bitlight Labs、LNP/BP Standards Association 以及 Dr. Maxim Orlovsky 在 RGB 生态建设中的贡献。本文所有验证都基于这些团队开源的代码和文档。
诚实声明
Stash Labs 团队在 RGB 领域仍处于学习阶段,知识储备与实践经验尚有不足,文中内容可能存在错误或疏漏,欢迎同行通过任意渠道批评指正。
结语
通过这次深度探索,我们不仅理解了 RGB 智能合约的底层机制,更看到了客户端验证这条技术路线的独特优势: 图灵完备的 AluVM 支持实现任意复杂逻辑
从 v0.11 的专用指令,到 v0.12 的通用寄存器机,再到未来 Contractum 的高级语言,RGB 的智能合约能力正在不断演进。我们期待 Contractum 编译器早日发布,让更多开发者能用声明式语言编写 RGB 合约,而不用手写汇编。