Skip to content

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 生态的开发者和研究者,我们发现社区普遍存在以下困惑:

  1. RGB 智能合约到底是用什么语言写的?
  2. v0.11 和 v0.12 智能合约有什么本质差异?
  3. 未来的 Contractum 智能合约语言 是什么?
  4. 客户端验证的底层机制是怎样的?

与其停留在文档阅读和理论推导,我们决定亲手编写两个版本的 RGB 合约的完整代码,跑通从发行到转账的全流程,用实践验证来回答这些问题。

验证范围说明 ​

我们选择 RGB20 同质化代币 作为对照案例,在 v0.11 和 v0.12 上分别手写最小实现

为什么选择 RGB20?

  1. RGB20(资产守恒:转账前后代币总量相等)是两个版本官方都有对应实现的案例(v0.11 的 NIA、v0.12 的 RGB20-FNA),对照更直接
    • NIA (Non-Inflatable Asset):v0.11 官方参考实现,不可增发资产
    • RGB20-FNA (Fungible Non-inflatable Asset):v0.12 官方参考实现,同质化不可增发资产
  2. 「转账守恒」这个不变量足够简单,能在两套完全不同的验证机制上分别真实实现

第一部分:实践验证过程 ​

我们详细记录亲手编写两代合约的完整过程,这是本文说服力的来源。

v0.11 实践:RGB-WG 官方 v0.11.0-beta.9 ​

验证环境 ​

crate版本来源
rgb-core0.11.0-beta.9github.com/RGB-WG/rgb-core
rgb-std0.11.0-beta.9github.com/RGB-WG/rgb-std
rgb-interfaces0.11.0-beta.9github.com/RGB-WG/rgb-interfaces
aluvm0.11.0-beta.9RGB-WG 生态同期发布

代码位置:v0.11-rgb20/(rgb-smart-contract-demo)

合约结构:Schema + Iface + IfaceImpl ​

v0.11 的合约由三个对象共同定义:

text
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):

rust
// 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 专用指令 ​

rust
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:构造合约实例

rust
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)

rust
let valid = run_transfer(
    &schema, &iface, &iimpl, &types, &scripts,
    70_000u64,  // 输入
    &[10_000u64, 60_000u64],  // 输出
);

结果:✅ 通过验证(pcvs 指令确认承诺之和相等)

步骤 3:无效转账(spend 10000 但 output 999999)

rust
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版本
aluvm0.12.0
ultrasonic0.12.0
sonic-api0.12.0
hypersonic0.12.0
sonic-persist-fs0.12.0

代码位置:v0.12-rgb20/(rgb-smart-contract-demo)

合约结构:Codex + Api + Semantics ​

v0.12 把"条款"和"界面"彻底分离:

text
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):

rust
// 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):

rust
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:构造合约实例

rust
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)

rust
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)

rust
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 行):

rust
let code = rgbasm! {
    put     a8[0],ERRNO_NON_EQUAL_IN_OUT;
    pcvs    OS_ASSET;  // ← 协议内置专用指令
    test;
    ret;
};

v0.12(26 行):

rust
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 行,未来):

contractum
verify {
    inputs.balance.sum() == outputs.balance.sum()
}

状态定义对比 ​

v0.11(Schema + Iface + IfaceImpl 三层):

rust
// 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 两层):

rust
// Codex: 只有路由
verifiers: tiny_bmap! {
    CALL_TRANSFER => transfer_site,
},

// Api: 状态定义
global: tiny_bmap! {
    vname!("_ticker") => GlobalApi { sem_id: types.get("RGB20.Ticker"), .. },
},

Contractum(一层,未来):

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) ​

rust
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) ​

rust
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! 同样是编译期宏(来自 ultrasonic crate),把 cknxi/jif/jmp/add/eq 展开成 Vec<Instr<LibId>>
  • 你看到的 loop/label 不是 Rust 的控制流,是汇编里的标签(goto 目标)
  • jif/jmp 通过这些标签的索引实现跳转

类比:

rust
// 这是 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 行指令 ​

rust
pcvs OS_ASSET;  // ← Pedersen 承诺求和验证(一条指令完成)

v0.12 的守恒验证:26 行循环 ​

rust
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 内部做了什么(协议层实现):

  1. 遍历所有 OS_ASSET 类型的输入 UTXO
  2. 提取每个输入的 Pedersen 承诺值
  3. 累加所有输入的承诺(椭圆曲线点加法)
  4. 遍历所有 OS_ASSET 类型的输出 UTXO
  5. 累加所有输出的承诺
  6. 比较两个承诺点是否相等

这 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 的 pcvsv0.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 的索引访问模式:

rust
// 理论上可行,但 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 的迭代器模式:

rust
// uasm! 原生支持
rsti destructible;         // 重置输入迭代器
loop LOOP:
    cknxi destructible;    // 检查是否有下一个
    jif CO, EXIT;
    ldi destructible;      // 自动加载 + 移动迭代器
    add E2, EB;            // 累加
    jmp LOOP;

rgbasm! 宏的限制 ​

v0.11 的 rgbasm! 仅暴露这些 RGB 扩展指令:

rust
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.11v0.12
控制流图灵完备性✅ 支持 jmp/jif/loop/label✅ 支持
算术能力✅ 完整的加减乘除比较✅ 完整
状态遍历能力❌ 无迭代器,需手动索引✅ 迭代器模式
汇编宏支持⚠️ 核心指令缺失助记符✅ 完整暴露
实际可写性❌ 只能用专用指令 pcvs✅ 可手写任意逻辑

结论:

  1. 底层能力:v0.11 的 AluVM + RgbIsa 包含图灵完备的控制流和算术指令
  2. 关键缺失:缺少迭代器模式(cknxi/ldi)导致状态遍历需要手动索引管理,且 rgbasm! 未暴露必要的计数/加载指令助记符
  3. 实际结果:v0.11 开发者无法像 v0.12 那样手写守恒循环,只能依赖 pcvs/pcas 等专用指令

这解释了为什么 v0.11 官方示例都使用 pcvs,而不是手写循环——不是不想,而是工具链不支持。

发现 4:v0.11 和 v0.12 是两条并行路线,Contractum 选择了 v0.12 ​

这是对生态合作方和开发者最重要的结论。

关键事实:

  1. v0.11 和 v0.12 不是版本升级关系,而是两条并行存在的技术栈
  2. Contractum 编译器生成的是 v0.12 风格的通用指令(cknxi/ldi/jif/jmp),而不是 v0.11 的专用指令(pcvs)
  3. 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 示例(未来,开发中) ​

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-20242024-2026开发中(未来)
核心库rgb-core 0.11SONIC/UltraSONICContractum 编译器
开发语言AluVM 汇编(rgbasm!)AluVM 汇编(uasm!)Contractum 高级语言
代码量(RGB20 守恒)4 行(pcvs 专用指令)26 行(手写循环)~5 行(.sum() 聚合)
图灵完备性有限(依赖预定义指令)是(手写循环+跳转)是(编译器生成循环)
金额保密性Pedersen 承诺(盲化)明文(本次验证)Zk64 类型(预期支持)
适用场景简单代币(RGB20/RGB21)复杂合约(AMM/DAO)所有 RGB 合约
底层虚拟机AluVM + RGB ISAAluVM + UltraSONICAluVM(编译目标)

演进逻辑:

  • v0.11 → v0.12:从专用指令到通用寄存器机,牺牲简洁性换取灵活性
  • v0.12 → Contractum:从手写汇编到高级语言,用编译器复杂度换取开发体验

第三部分:给开发者和生态合作方的提示 ​

对 RGB 开发者 ​

  1. 理解底层再用高级工具:虽然未来有 Contractum,但理解 AluVM 汇编有助于调试和优化
  2. v0.11 和 v0.12 特征:
    • 简单代币 → v0.11 的 pcvs 足够(4 行代码)
    • 复杂逻辑(AMM/DAO/条件转账)→ 必须用 v0.12

对协议研究者 ​

  1. 客户端验证的确定性来源:AluVM 字节码跨平台执行结果一致
  2. 图灵完备性的实证:v0.12 的 jif/jmp 循环是真实的图灵完备实现
  3. 保密交易的两种路线:
    • v0.11:Pedersen 承诺(协议层原生支持)
    • v0.12:本次验证用明文,但预期 Contractum 的 Zk64 类型会支持

我们验证了什么 ​

✅ v0.11 的 pcvs 指令能正确拒绝不守恒转账

✅ v0.12 的手写循环实现了图灵完备的验证逻辑

✅ 对比了两代技术的实际差异(代码量、灵活性、隐私性)

✅ 澄清了 Rust 和汇编的分层关系(验证逻辑是汇编,Rust 是工具)

参考资料 ​

官方文档 ​

代码仓库 ​

学术论文 ​

组织仓库 ​

致谢 ​

感谢 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 合约,而不用手写汇编。

交易并保护您的 RGB 资产 | 释放 RGB 协议的全部潜力