Skip to content

概述


什么是 UTXO_Compiler

UTXO_Compiler(可执行文件名 utxo_compiler)是一个面向比特币 UTXO 模型的智能合约编译器。它接受以 .ct 为扩展名的高级合约源文件,将其编译为可嵌入比特币交易锁定脚本的 BVM 字节码。

合约语言的语法脱胎于 Python——使用缩进划分代码块、def 定义函数——但加入了强制类型声明和一套编译期所有权检查机制,帮助开发者在部署前发现数据误用问题。


比特币智能合约的运作方式

UTXO 模型

与以太坊的账户模型不同,比特币采用 UTXO(Unspent Transaction Output,未花费交易输出) 模型。每一笔比特币交易消耗若干旧的 UTXO,同时产生新的 UTXO。

每个 UTXO 携带两样东西:

  • 聪(satoshis):这个输出锁定了多少比特币
  • 锁定脚本(locking script):一段字节码,规定了"什么条件下才能花费这笔钱"

花费 UTXO 时,发起方需要在交易输入中提供解锁脚本(unlocking script)。只有当解锁脚本与锁定脚本联合执行后结果为真,花费才被网络认可。

  上一笔交易的输出                当前交易
  ┌──────────────────┐           ┌──────────────────────┐
  │ value: 100000聪   │ ─────────▶│ 输入:解锁脚本(钥匙) │
  │ 锁定脚本(密码锁) │           │ 输出:新的锁定脚本    │
  └──────────────────┘           └──────────────────────┘
         锁                              钥匙 + 新锁

智能合约就是在锁定脚本里写入自定义逻辑:不只是验证签名,还可以验证时间、数据格式、多方条件等任意可编程条件。

BVM 是什么

BVM(Bitcoin Virtual Machine)是执行锁定脚本/解锁脚本的底层虚拟机,是一台基于栈的状态机。它没有堆,没有全局变量,只有两个栈(主栈和副栈)和一组操作码。

UTXO_Compiler 将你的高级合约代码翻译成 BVM 能直接执行的操作码序列。理解 BVM 的栈模型,是读懂编译器行为和所有权系统的关键。更多内容见 比特币基础


你可以用它写什么

.ct 合约不只是能写单个签名校验,也适合表达多种常见的 UTXO 花费条件:

场景合约主要验证什么
签名与身份锁签名、公钥、预期公钥哈希
哈希锁与时间锁哈希原像、截止时间、退款路径和分支花费规则
多方条件多公钥、阈值逻辑、承诺数据和定长参数数组
有状态 UTXO 流程被花费 UTXO 中的旧状态,以及新输出中的下一状态
预言机结算外部签名消息、字段解析和收款方选择
交易级约束输入承诺、输出布局、手续费、退款和资产流向规则

这些场景共享同一个模型:合约不会修改隐藏存储;它只证明“当前这次花费是否合法”,必要时再证明“当前交易是否生成了正确的下一枚 UTXO”。


UTXO_Compiler 是如何工作的

  合约文件 (.ct)


      编译


  JSON 输出
  - 锁定脚本字节码
  - 函数参数信息
  - self.X 占位符
  - 可选调试信息


  用真实数据实例化


  比特币锁定脚本


  创建并花费 UTXO

从用户视角看,编译器会把带类型的 .ct 合约转换成可用于实例化锁定脚本的 JSON。编译过程中会检查语法、类型、变量所有权、函数参数,以及脚本中数据传递的形状。

导出的 JSON 是部署流程的桥梁:它告诉你要使用哪段字节码、解锁脚本需要提供哪些参数,以及哪些 self.X 值需要在上链前填入真实数据。


语言的核心设计取舍

显式类型声明

所有函数参数、结构体字段都必须声明类型。没有自动类型推断,没有 var / auto。这看起来有些繁琐,但在 BVM 这种零容错环境里,"你在代码里看到什么,栈上就发生什么"是比简洁更重要的属性。

编译期所有权检查

局部变量在被传入内置函数或赋值给其他变量后,原变量即被"消耗",之后不能再次引用。这个机制直接对应 BVM 的栈弹出语义——在底层,变量被使用就意味着栈上的值被弹走了。所有权系统让编译器在编译期就捕获这类错误,而不是等到链上执行时出错。

合约状态固化在字节码中

合约成员变量会被直接编译进字节码本身,需要用户在字节码中用不同数据替换以生成不同的锁定脚本。这是 UTXO 模型"状态即代码"特性的直接体现,与以太坊那种"合约地址 + 链上存储"的模型有根本区别。

公有函数按顺序串行执行

不以下划线开头的函数会进入 ABI,但它们会按声明顺序编译成一条连续的锁定脚本流程。互斥路径不应写成多个 public 方法;通常用一个 main(path, ...) 公有入口,在内部用私有函数分发不同路径。

单合约文件

每个 .ct 文件包含且仅包含一个合约。这与"一个 UTXO 对应一段锁定脚本"的模型天然对齐。

标准库与模板复用

合约文件可以通过 import std.p2pkh 复用标准模板,解析路径来自标准库根目录。导入的库函数会在编译期对当前合约可用,并按私有辅助函数处理,不会作为 ABI 花费入口导出。实例数据通常由宿主合约显式传参,例如 verifyP2PKH(sig, pubKey, self.pubKeyHash)


推荐开发流程

  1. 编写一个 .ct 文件,只定义一个 Contract,并为需要读取的交易片段声明清晰的 Struct
  2. self.X 占位符表达实例数据,例如公钥哈希、截止时间、预言机公钥或协议常量。
  3. 使用 utxo_compiler your_contract.ct 编译;需要逐步排查时打开调试输出。
  4. 检查导出的 JSON、字节码、函数参数和占位符。
  5. 用真实数据替换 self.X,实例化锁定脚本,并用该脚本创建 UTXO。
  6. 构造花费交易,让解锁脚本提供函数参数,同时让交易输出满足 BVM.outputsHash 等约束。

如果是有状态合约,常见模式是:用 Struct 建模父交易,重建被花费的 preTXID,从 SuffixData 读取旧状态,再验证新输出延续了相同合约代码并写入了正确的新状态。


与其他方案的横向对比

UTXO_Compiler .ctsCrypt (BSV)Bitcoin Script(原生)
抽象层级高级语言高级语言汇编级
语法风格Python-likeTypeScript-like操作码序列
类型系统强类型强类型无类型
所有权检查✓ 编译期
调试工具内置交互式调试器插件支持
实例数据self.X / 后缀数据构造函数 / 属性手动压栈
多路径入口串行流程;用 path 分发多个 public method手写分支脚本

初级开发者易踩坑

  • 把 UTXO 合约当成以太坊合约:UTXO 合约没有可随时读写的链上存储,状态通常写在下一枚 UTXO 的脚本里。
  • 忽略 self.X 的实例化步骤:编译输出只是模板,上链前还要填入真实数据。
  • 把多个 public 函数当成“任选一个调用”:它们会按声明顺序连续执行。
  • 只看合约代码,不看交易输出:有状态合约必须同时检查当前交易是否创建了正确的新输出。

快速回顾

  • UTXO_Compiler 把 .ct 合约编译成可放进交易输出的锁定脚本字节码。
  • 合约不是修改隐藏存储,而是验证“这次花费是否符合条件”。
  • self.X 表示部署时写入锁定脚本的实例数据。
  • 公有函数会按声明顺序串行执行;互斥路径通常用一个入口加 path 参数分发。
  • 写有状态合约时,要同时验证旧 UTXO 和当前交易的新输出。

下一步


🇬🇧 English version