1. 字节雪球爱好者社区首页
  2. 基本原理

ByteBall原理解析(三)地址、脚本及合约

Byteball的地址脚本合约

地址的定义

Byteball中用户使用地址进行收发交易。地址本质上对应的是一段具有特定含义的脚本,该脚本称为地址的定义。任何能够使地址定义脚本输出为真(也称作解锁该脚本)的人具有使用该地址资产的权限。与Bitcoin类似,最常用的地址定义脚本是公钥(采用BASE64编码),即具有相应私钥的人可以使用该地址的资产,比如

["sig",{"pubkey":"Ald9tkgiUZQQ1djpZgv2ez7xf1ZvYAsTLhudhvn0931w"}]

对于地址定义脚本进行哈希,再加上校验位就得到了地址,Byteball的地址采用BASE32编码。Byteball地址的校验位并不是全部放在尾部,而是穿插着放在哈希值中间,防止有攻击者在地址中间进行恶意修改。

ByteBall原理解析(三)地址、脚本及合约

按照此流程,上面公钥脚本对应的地址为:

A2WWHN7755YZVMXCBLMFWRSLKSZJN3FU

如果地址仅用于接收交易,其定义脚本可以不对外公布。但是当用户首次使用该地址进行发送交易时,他需要在发送的单元中声明该地址的定义脚本,比如

unit: {
  ...
  authors: [ {
    address: 'DJ6LV5GPCLMGRW7ZB55IVGJRPDJPOQU6',
    definition: [
      "sig", {"pubkey":"AsnvZ3w7N1lZGJ+P+bDZU0DgOwJcGJ51bjsWpEqfqBg6"}
    ],
    authentifiers: {
      r: '3eQPIFiPVLRwBwEzxUR5thqn+zlFfLXUrzAmgemAqOk35UvDpa4h79Fd6TbPbGfb8VMiJzqdNGHCKyAjl786mw=='
    }
  } ],
  ...
}

其中,authentifiers是用户采用私钥对除authentifiers之外的数据进行的签名。在用户使用该地址首次发送单元之后,它不允许再发送地址的定义。当然,只有在该地址的第一个单元到达稳定后,用户才可以发送后续单元。

用户可以在保持地址不变的条件下修改地址的定义脚本,用户需要发送消息

unit: {
  ...
  messages: [
    ...
    {
      app: "address_definition_change",
      definition_chash: "I4Z7KFNIYTPHPJ5CA5OFC273JQFSZPOX"
    },
    ...
  ],
  ...
}

definition_chash为新的地址定义脚本生成的地址。那么,下一个从原地址发出的单元有以下两条要求:

  1. 必须把address_definition_change这个单元作为其last_ball
  2. 在修改地址定义脚本后发出第一个单元时,需要把新的定义脚本作为第一条message

显然,新的地址定义脚本生成的地址跟原地址是不相同的。当用户迁移到新的设备上,同时想保持地址不变时,可以使用这种方式来修改地址定义脚本。

地址定义脚本中必须显式地(使用sig)或隐式地(使用address)包含至少一个sig。为了防止消耗过量的资源,脚本的操作总数限制在100以内,包括授权地址及脚本模板中的所有操作。

相比于Ethereum,Byteball的脚本语言的解释能力有限,它定义的几乎都是逻辑判断语句。但是,Byteball本身是为了提供给那些并不太懂编程的人群使用的,其语言必须便于理解且不容易出错。

逻辑运算脚本

与运算:当多个条件同时满足时,脚本输出为真。比如,同时需要两个私钥签名的脚本

["and", [
  ["sig", {pubkey: "one pubkey in base64"}],
  ["sig", {pubkey: "another pubkey in base64"}]
]]

或运算:多个条件中有一个满足时,脚本输出为真。比如,仅需要laptopsmartphone或者talet中某一个私钥就可以解锁的脚本

["or", [
  ["sig", {pubkey: "laptop pubkey"}],
  ["sig", {pubkey: "smartphone pubkey"}],
  ["sig", {pubkey: "tablet pubkey"}]
]]

非运算:脚本中不含sighashaddresscosigned by或者in merkle的条件可以进行非运算,比如

["not", [
  "in data feed",
  [["NOAA ADDRESS"], "wind_speed", ">", "200"]
]]

逻辑嵌套:逻辑运算可以嵌套使用。比如,必须同时拥有smartphone私钥以及laptop或者tablet中某一个私钥就可以解锁的脚本

["and", [
  ["or", [
    ["sig", {pubkey: "laptop pubkey"}],
    ["sig", {pubkey: "tablet pubkey"}]
  ]],
  ["sig", {pubkey: "smartphone pubkey"}]
]]

最小数量运算:当满足条件的个数超过门限时,脚本输出为真。比如,具有2个以上私钥就可以解锁的脚本

["r of set", {
  required: 2,
  set: [
    ["sig", {pubkey: "laptop pubkey"}],
    ["sig", {pubkey: "smartphone pubkey"}],
    ["sig", {pubkey: "tablet pubkey"}]
  ]
}]

最低权重运算:当满足条件的权重值超过门限时,脚本输出为真。比如,当几个私钥签名的权重之和大于50时可以解锁的脚本

["weighted and", {
  required: 50,
  set: [
    {weight: 40, value: ["sig", {pubkey: "CEO pubkey"}] },
    {weight: 20, value: ["sig", {pubkey: "COO pubkey"}] },
    {weight: 20, value: ["sig", {pubkey: "CFO pubkey"}] },
    {weight: 20, value: ["sig", {pubkey: "CTO pubkey"}] }
  ]
}]

地址授权脚本

授权使用其它地址来解锁脚本,其定义的语法为

["and", [
  ["address", "ADDRESS 1 IN BASE32"],
  ["address", "ADDRESS 2 IN BASE32"]
]]

这可以很方便地用来构造共享控制的地址。比如,上面给出的地址定义脚本生成的地址将由ADDRESS1ADDRESS2共同控制。

共同签名脚本

要求与另一个地址共同签名才可以解锁脚本

["cosigned by", "ANOTHER ADDRESS IN BASE32"]

地址已用脚本

要求由某个地址发出的单元至少有一个成为last_ball_unit

["seen address", "ANOTHER ADDRESS IN BASE32"]

数据订阅脚本

通过订阅的数据是否符合条件来解锁脚本,其语法格式为

["in data feed", [
  ["ADDRESS1", "ADDRESS2", ...],
   "data feed name",
   "=",
   "expected value"
]]

上述脚本表示:当数据源地址ADDRESS1ADDRESS2等中某个地址发出的消息中订阅数据data feed name等于expected value时,脚本输出为真。

地址发出的数据订阅消息格式为

unit: {
  ...
  messages: [
    ...
    {
      app: "data_feed",
      payload_location: "inline",
      payload_hash: "hash of payload",
      payload: {
        "data feed name": "value",
        "another data feed name": "value2",
        ...
      }
    },
    ...
  ],
  ...
}

对赌合约

当某个地址可以作为可靠的数据订阅源时,用户可以使用其作为外部数据条件来构造合约。比如,

["or", [
  ["and", [
    ["address", "ADDRESS 1"],
    ["in data feed", [["EXCHANGE ADDRESS"], "EURUSD", ">", "1.1500"]]
  ]],
  ["and", [
    ["address", "ADDRESS 2"],
    ["in data feed", [["TIMESTAMPER ADDRESS"], "datetime", ">", "2016-10-01 00:00:00"]]
  ]]
]]

上述脚本给出了ADDRESS 1ADDRESS 2之间的一个简单合约,假设其对应的地址为ADDRESS X。当EXCHANGE ADDRESS发布的汇率数据EURUSD大于1.1500时,仅使用ADDRESS 1的私钥就可以取走ADDRESS X中的资产。而当TIMESTAMPER ADDRESS发布的时间数据datetime大于2016-10-01 00:00:00时,仅使用ADDRESS 2的私钥就可以取走ADDRESS X中的资产。也就是说,上述脚本定义的是对赌合约:如果2016-10-01 00:00:00之前EURUSD汇率超过1.1500,地址ADDRESS 1获胜,否则地址ADDRESS 2获胜。

商品合约

当顾客购买商品时,也可以使用上述方式来制定合约,比如

["or", [
  ["and", [
    ["address", "MERCHANT ADDRESS"],
    ["in data feed", [["FEDEX ADDRESS"], "tracking", "=", "123456"]]
  ]],
  ["and", [
    ["address", "BUYER ADDRESS"],
    ["in data feed", [["TIMESTAMPER ADDRESS"], "datetime", ">", "2016-10-01 00:00:00"]]
  ]]
]]

上述脚本给出了顾客BUYER ADDRESS和商户MERCHANT ADDRESS之间的合约,假设其对应的地址为ADDRESS Y。顾客在购买商品时,将款项打入地址ADDRESS Y。如果快递公司FEDEX ADDRESS发布数据表明相应的快递已签收,则商户MERCHANT ADDRESS可以从ADDRESS Y中取走货款;如果TIMERSTAMPER ADDRESS发布的时间数据datetime大于2016-10-01 00:00:00时,则顾客BUYER ADDRESS可以从ADDRESS Y中取回货款。

上述场景中,快递公司需要对每一个快递都发布其签收状态数据,这将需要发布大量的数据。Merkle数据订阅可以降低需要发布的数据量。只需要核实关心的hash值出现在数据源地址发布的Merkle树中时,即可证明该事件已发生。其定义语法如下:

["in merkle", [
  ["ADDRESS1", "ADDRESS2", ...],
   "data feed name",
   "hash of expected value"
]]

此时,快递公司只需要定期将一大批快递状态构造Merkle树,并发布Merkle根即可。商户可以通过相应快递的Merkle路径来解锁Merkle数据订阅的脚本。

单元约束脚本

脚本可以对相应地址发出的单元数据进行约束,其定义格式为:

['has', {
  what: 'input'|'output',
  asset: 'assetID in base64 or "base" for bytes',
  type: 'transfer'|'issue',
  own_funds: true,
  amount_at_least: 123,
  amount_at_most: 123,
  amount: 123,
  address: 'INPUT OR OUTPUT ADDRESS IN BASE32'
}]

上述脚本要求单元至少有一个输入/输出满足后续定义所有的条件。特别地,可以使用has one来强制要求有且仅有一个输入/输出满足后续所有条件。

其它类似的约束还有求和约束,要求输入/输出之和满足特定条件,其格式为

['sum', {
  filter: {
    what: 'input'|'output',
    asset: 'asset or base',
    type: 'transfer'|'issue',
    own_funds: true,
    address: 'ADDRESS IN BASE32'
  },
  at_least: 120,
  at_most: 130,
  equals: 123
}]

交易合约

单元约束脚本可以用来实现去中心化交易。假设用户USER ADDRESS希望使用不高于1000bytes的价格购买1200units的其它资产。用户可以发送1000bytes至如下脚本定义的地址上:

["or", [
  ["address", "USER ADDRESS"],
  ["and", [
    ["address", "EXCHANGE ADDRESS"],
    ["has", {
      what: "output",
      asset: "ID of alternative asset",
      amount_at_least: 1200,
      address: "USER ADDRESS"
    }]
  ]]
]]

或逻辑or的第一个条件表明,在未成交之前,用户可以随时取回他的1000bytes。或逻辑or的第二个条件表明,其他用户可以使用EXCHANGE ADDRESS地址私钥来取走着1000bytes,只要他同时在同一单元中将1200units其它资产输出到USER ADDRESS。通过这种方式,用户之间可以实现不同资产之间的交易。

借贷合约

单元约束脚本还可以用来实现抵押借贷。假设借款人抵押某种资产借贷10000bytes,那么借款人和借贷人可以共同签名一笔交易,其中借贷人将bytes发送给借款人,同时借款人将抵押资产转入以下脚本定义的地址上:

["or", [
  ["and", [
    ["address", "LENDER ADDRESS"],
    ["in data feed", [["TIMESTAMPER ADDRESS"], "datetime", ">", "2017-06-01 00:00:00"]]
  ]],
  ["and", [
    ["address", "BORROWER ADDRESS"],
    ["has", {
      what: "output",
      asset: "base",
      amount: 10000,
      address: "LENDER ADDRESS"
    }]
  ]],
  ["and", [
    ["address", "LENDER ADDRESS"],
    ["address", "BORROWER ADDRESS"]
  ]]
]]

上述脚本包括了三层含义:

  1. 当时间超过2017-06-01 00:00:00时,借贷人可以取走抵押资产;
  2. 当借款人归还10000bytes至借贷人地址LENDER ADDRESS时,借款人可以取回抵押资产;
  3. 借贷人和借款人可以协商解除合约。

脚本模板

通过预先设定的脚本模板可以很方便地定义脚本,只需要对模板中的参数进行修改即可

["definition template", [
  "hash of unit where the template was defined",
  {param1: "value1", param2: "value2"}
]]

脚本模板需要在单元中发送app=’definition_template’的消息,并且需要单元到达稳定状态后,脚本模板才可以使用。消息内容与普通的地址定义脚本相同,参数使用@param1@param2表示。

版权所有。发布者:guantau,转载请注明出处:https://bbfans.org/2018/05/21/byteball-address-script-contract/

发表评论

登录后才能评论

评论列表(1条)

联系我们

加入ByteBall技术群请添加

QR code