Skip to content

脚本语境

在 UTXO 模型中,验证智能合约的上下文是包含它的 UTXO 和花费它的交易,包括它的输入和输出。在以下示例中,当 tx1 的第二个输入是 tx0 的第二个输出时(3 个输入和 3 个输出),智能合约的上下文大致是包含它的 UTXO 和 tx1 中被圈出的部分。

scriptContext

上下文仅包含本地信息。 这与基于账户的区块链(如 Ethereum)不同,后者包含整个区块链的全局状态。 所有智能合约共享一个全局状态会危及可扩展性,因为交易必须顺序处理,从而导致潜在的竞争条件。

这个上下文在 ScriptContext 接口中表达。

ts
export interface ScriptContext {
  // 交易版本号
  version: ByteString,
  // 这个交易输入花费的特定 UTXO
  utxo: UTXO,
  // 所有输入的序列化输出的双 SHA256 哈希
  hashPrevouts: ByteString,
  // 所有输入的序列化输入序列值的双 SHA256 哈希
  hashSequence: ByteString,
  // 交易输入的序列号
  sequence: bigint,
  // 所有输出序列化输出金额的双 SHA256 哈希
  hashOutputs: ByteString,
  // 交易锁定时间
  locktime: bigint,
  // 此输入使用的 SIGHASH 标志
  sigHashType: SigHashType,
  // 获取整个序列化的 sighash 原像
  serialize(): SigHashPreimage,
}

export interface UTXO {
  // 锁定脚本
  script: ByteString,
  // 金额以 satoshis 为单位
  value: bigint,
  // 此 UTXO 引用的 outpoint
  outpoint: Outpoint,
}

export interface Outpoint {
  /** 持有该输出的交易的 txid */
  txid: ByteString,
  /** 特定输出的索引 */
  outputIndex: bigint,
}

下表显示了 ScriptContext 结构中每个字段的含义。

字段描述
version交易版本
utxo.value花费此输入的输出的金额
utxo.script此 UTXO 的锁定脚本
utxo.outpoint.txid被花费的交易的 txid
utxo.outpoint.outputIndexUTXO 在输出中的索引
hashPrevouts如果未设置 ANYONECANPAY SIGHASH 类型,则为所有输入 outpoints 的序列化的双 SHA256。否则,它是一个 uint2560x0000......0000
hashSequence如果未设置 ANYONECANPAYSINGLENONE SIGHASH 类型,则为所有输入的序列化 sequence 的双 SHA256。否则,它是一个 uint2560x0000......0000
sequence输入的序列号
hashOutputs如果 SIGHASH 类型 不是 SINGLE 也不是 NONE,则为所有输出的序列化的双 SHA256。如果 SIGHASH 类型SINGLE 并且输入索引小于输出的数量,则为与输入具有相同索引的输出的双 SHA256。否则,它是一个 uint2560x0000......0000
locktime交易的锁定时间
sigHashType签名的 sighash 类型

你可以在任何公共 @method 中通过 this.ctx 直接访问上下文。它可以被视为公共方法在调用时除了其函数参数之外的额外信息。

以下示例访问了花费交易的 locktime。合约是一个时间锁,只能在成熟时间之后调用。

ts
class TimeLock extends SmartContract {
  @prop()
  readonly matureTime: bigint // 可以是时间戳或块高。

  constructor(matureTime: bigint) {
    super(...arguments)
    this.matureTime = matureTime
  }

  @method()
  public unlock() {
    assert(this.ctx.locktime >= this.matureTime, "locktime too low")
  }
}

访问输入和输出

花费交易的输入和输出不直接包含在 ScriptContext 中,而是它们的哈希/摘要。要访问它们,我们可以先构建它们,然后验证哈希到预期的摘要,这确保它们实际上来自花费的交易。

以下示例确保 Alice 和 Bob 从合约中获得 1000 聪。

ts
class DesignatedReceivers extends SmartContract {
  @prop()
  readonly alice: Addr

  @prop()
  readonly bob: Addr

  constructor(alice: Addr, bob: Addr) {
    super(...arguments)
    this.alice = alice
    this.bob = bob
  }

  @method()
  public payout() {
    const aliceOutput: ByteString = Utils.buildPublicKeyHashOutput(this.alice, 1000n)
    const bobOutput: ByteString = Utils.buildPublicKeyHashOutput(this.bob, 1000n)
    let outputs = aliceOutput + bobOutput

    // 需要一个找零输出
    outputs += this.buildChangeOutput();

    // 确保输出实际上是花费交易的一部分
    assert(this.ctx.hashOutputs == hash256(outputs), 'hashOutputs mismatch')
  }
}

前置输出

通过 ScriptContexthashPrevouts 字段,我们可以访问 Prevouts 的哈希:

如果未设置 ANYONECANPAY 标志,hashPrevouts 是所有输入 outpoints 的序列化的双 SHA256; 否则,hashPrevouts 是一个 uint2560x0000......0000

我们可以通过 this.prevouts 访问完整的 prevouts。

  • 如果未设置 ANYONECANPAY 标志,this.prevouts 的哈希等于 this.ctx.hashPrevouts
  • 否则,this.prevouts 将为空。

SIGHASH 类型

SIGHASH 类型 决定了哪些部分的交易包含在 ScriptContext 中。

sighashtypes

默认情况下,它是 SigHash.ALL,包括所有输入和输出。你可以通过设置 @method() 装饰器的参数来自定义它,如下所示:

ts
@method(SigHash.ANYONECANPAY_ALL)
public increment() {
  ...
}

@method(SigHash.ANYONECANPAY_NONE)
public increment() {
  ...
}

@method(SigHash.ANYONECANPAY_SINGLE)
public increment() {
  ...
}

有 6 种 SIGHASH 类型可供选择:

SIGHASH 类型功能意义
ALL签名所有输入和输出
NONE签名所有输入和没有输出
SINGLE签名所有输入和具有相同索引的输出
ANYONECANPAY_ALL签名自己的输入和所有输出
ANYONECANPAY_NONE签名自己的输入和没有输出
ANYONECANPAY_SINGLE签名自己的输入和具有相同索引的输出

有关更多信息,请参阅 Sighash 类型 部分。

序列化

你可以选择将 this.ctx 转换为 SigHashPreimage 对象通过序列化。这可以通过调用 this.ctx.serialize() 方法来实现。输出对象遵循在交易签名或验证期间使用的格式。

text
nVersion of the transaction (4-byte little endian)
hashPrevouts (32-byte hash)
hashSequence (32-byte hash)
outpoint (32-byte hash + 4-byte little endian)
scriptCode of the input (serialized as scripts inside CTxOuts)
value of the output spent by this input (8-byte little endian)
nSequence of the input (4-byte little endian)
hashOutputs (32-byte hash)
nLocktime of the transaction (4-byte little endian)
sighash type of the signature (4-byte little endian)

Source

一个值得注意的应用是序列化原像在创建自定义 SigHash 标志中的应用。一个例子是 SIGHASH_ANYPREVOUT,它展示了这一过程。

调试

请参阅 如何调试 ScriptContext 失败