赔付曲线序列化

该文档详细介绍了在数字结果的 DLC (Discreet Log Contract) 中,如何对赔付曲线进行序列化和反序列化。针对一般的赔付曲线,文档提出了"General Payout Curves"的概念,并详细说明了其序列化方法和函数评估过程。此外,对于特定的双曲线形状的赔付曲线,文档也提供了相应的序列化和评估方法,最后强调了这些设计是为了提高效率和紧凑性,从而更方便地应用到实际场景中。

赔付曲线序列化

简介

当为一个数值结果构建 DLC 时,通常会有数量非常多的 可能结果,以至于实际上无法在要约中全部枚举出来,以及它们相关的赔付。

通常情况下,存在一些宏观结构,其中一些参数决定了 所有可能结果的赔付,使得这些参数成为数值结果 DLC 赔付曲线的更简洁的序列化方式。

本文档首先详细说明了所谓的 通用赔付曲线的序列化和反序列化(又名评估),这应该足以满足任何简单的赔付曲线,例如那些 由一些线条组合而成的曲线,以及不需要创建自己的类型的自定义赔付曲线。

本文档还详细说明了双曲线形状的赔付曲线片段的序列化和反序列化, 这对于许多反向合约(例如差价合约 (CFD))很有用,其中赔付 曲线的形式为 constant/outcome(其中 outcome 是输入值)。

目录

通用赔付曲线

设计

本规范的目标是高效、紧凑地实现通用赔付曲线形状,同时也确保 更简单、更常见的赔付曲线(例如远期合约的直线)在符合通用结构的同时不会变得复杂。

具体来说,通用赔付曲线规范支持分段函数集(片段之间没有连续性 要求),其中片段是多项式或双曲线。

如果你希望支持的某些赔付曲线“形状”不能有效地表示为分段多项式 函数,那么你应该提出一个新的 payout_curve_piece 类型,该类型可以有效地指定最少的参数集, 从中可以确定整个赔付曲线片段。 例如,“形状” 1/outcome 仅由几个参数完全确定,但当使用多项式插值进行逼近时,可能需要数千个插值点, 因此引入了特定于双曲线的类型。

请注意,赔付曲线是协议级别的抽象,在应用程序层,用户很可能会 与某些合约模板上的一组参数进行交互。 他们不会直接与通用赔付曲线及其序列化进行交互,这些序列化旨在实现核心 DLC 逻辑实现之间的有效 序列化和确定性再现。 通用赔付曲线还处理大量常见应用程序用例的插值逻辑, 也就是说,应用程序可能不必计算所有结果点来序列化为此格式, 而是通常可以使用仅用户提供的参数来直接计算少量相关的插值点。

曲线序列化

在本节中,我们将详细介绍通用 payout_function 的 TLV 序列化。

payout_function
1. 数据:
   * [`bigsize`:`num_pieces`]
   * [`u64`:`endpoint_0`]
   * [`u64`:`endpoint_payout_0`]
   * [`u16`:`extra_precision_0`]
   * [`payout_curve_piece`:`piece_1`]
   * [`u64`:`endpoint_1`]
   * ...
   * [`payout_curve_piece`:`piece_num_pieces`]
   * [`u64`:`endpoint_num_pieces`]
   * [`u64`:`endpoint_payout_num_pieces`]
   * [`u16`:`extra_precision_num_pieces`]

num_pieces 是构成赔付曲线的 payout_curve_pieces 的数量及其端点。 每个端点由两个 u64 和一个 u16 组成。

第一个整数称为 endpoint,包含实际的 event_outcome,它对应于赔付曲线上的 x 坐标, 该坐标是曲线片段之间的边界。 第二个整数称为 endpoint_payout,如果签署了 endpoint 的值,则将其设置为等于本地方的赔付, 此赔付对应于赔付曲线上的 y 坐标。 第三个整数 u16 称为 extra_precision,并设置为二进制点之后四舍五入的赔付的前 16 位。 这种额外的精度确保了插值不会因 于舍入而导致的 endpoint_payout 中的错误而包含较大的误差。 准确地说,用于插值的点应该是: (endpoint, endpoint_payout + double(extra_precision) >> 16)。 对于本文档的其余部分,值 endpoint_payout + double(extra_precision) >> 16 称为 endpoint_payout

请注意,此 payout_function 是从提供者的角度来看的。 要评估接受者的 payout_function,你必须在给定的 event_outcome 处评估提供者的 payout_function,并从 total_collateral 中减去结果赔付。 重要的是,你不要通过将提供者的 payout_function 中的所有 payout 字段替换为 total_collateral - payout 并插值resulting 点来构造接受者的 payout_function。 这是行不通的,因为由于舍入,双方的 payout_function 的输出之和可能是 total_collateral - 1,包括检查此情况(任一结果都缺少一个 satoshi)到验证算法 可能会使验证时间最多慢四倍,并通过破坏一个合理的假设来增加协议的复杂性。

要求
  • num_pieces 必须至少为 1
  • endpoint 必须严格增加。 如果需要不连续性,则应使用“空” polynomial_curve_piece,从而导致 连续 endpoint 值之间的线。 这样做是为了避免对不连续点的值产生歧义。

通用函数评估

给定一个潜在的 event_outcome,按如下方式计算 outcome_payout

  • 通过 event_outcome 二分查找 endpoint
    • 如果找到,则返回 endpoint_payout
    • 否则,在先前和下一个 endpoint 之间(包括之前和之后)的 payout_curve_piece 上评估 event_outcome
参考实现

CET 计算期间的优化评估

当重复且顺序地评估插值(如在 CET 计算期间所做的那样)时,可以对这个分段插值函数进 行许多优化。

  • 为顺序输入计算时,可以避免二分查找。

  • 在评估多项式片段时,可以为多项式片段中的每个 i 缓存值 points(i).outcome_payout / PROD(j = 0, j < points.length && j != i, points(i).event_outcome - points(j).event_outcome),称之为 coef_i。 给定一个 event_outcome,令 all_prod = PROD(i = 0, i < points.length, event_outcome - points(i).event_outcome)

然后,可以将总和计算为 SUM(i = 0, i < points.length, coef_i * all_prod / (event_outcome - points(i).event_outcome))

  • 当引入精度范围时,曲线片段的导数可用于减少所需的计算量。 例如,当处理三次多项式片段时,如果你从左到右移动并在第一个导数(斜率)为正且第二个导数(凹度)为负时输入新的模精度值,那么你可以取此点处曲线的切线,并找到其与下一个模精度值边界的交点。 如果导数的符号相同,则从当前 x 坐标到交点的 x 坐标的间隔保证具有相同的模精度值。

赔付曲线片段

多项式曲线片段

由于直线是多项式,因此当以分段多项式函数的语言表示时,简单曲线仍然很简单。 任何有趣的(例如,非随机的)赔付曲线都可以使用巧妙构造的多项式插值紧密逼近。 最后,序列化这些函数可以通过仅提供每个多项式片段的几个点来紧凑地完成,以便 使通信中的接收方能够从这最少的信息中插值多项式。

但重要的是要注意,由于 Runge 现象, 客户端通常最好使用某些样条插值来构造其赔付曲线,而不是直接使用多项式插值(除非线性逼近就足够了)。 样条由多项式片段组成,因此可以将结果插值写为分段多项式。

多项式序列化
1. 实现:`payout_curve_piece`
2. 类型:0
3. 数据:
   * [`bigsize`:`num_pts`]
   * [`u64`:`event_outcome_1`]
   * [`u64`:`outcome_payout_1`]
   * [`u16`:`extra_precision_1`]
   * ...
   * [`u64`:`event_outcome_num_pts`]
   * [`u64`:`outcome_payout_num_pts`]
   * [`u16`:`extra_precision_num_pts`]

num_pts 是此曲线片段中指定的中间点的数量,这些中间点将与周围的 endpoint 一起用于执行插值。 每个点由两个 bigsize 整数和一个 u16 组成,它们的解释方式与 通用赔付曲线中的 endpoint 的解释方式完全相同,用作 xy 坐标。

num_pts0 的特殊情况下,仅使用端点,这意味着在端点之间插值一条直线。

多项式评估

有很多方法可以计算由某些插值点集确定的唯一多项式。 我选择在此处详细介绍 拉格朗日插值,因为它相对简单,但任何算法都应该有效,只要它不会导致近似误差太大,以至于无法通过验证。 仅举几个其他算法,如果你对替代方案感兴趣,你可能希望使用范德蒙矩阵或 另一种替代方案,除差法

请注意,虽然以下内容可能看起来很复杂,但它应该归结为非常少的代码行。 此外,这个问题是众所周知的,大多数语言的解决方案可能存在于 Stack Overflow 等站点上, 你可以从中复制它们,以便只需要进行细微的审美修改。

给定一个潜在的 event_outcome,按如下方式计算 outcome_payout(其中 points 是包含 endpoint 的列表):

  1. lagrange_line(i, j) = (event_outcome - points(j).event_outcome)/(points(i).event_outcome - points(j).event_outcome)
  2. lagrange(i) := PROD(j = 0, j < points.length && j != i, lagrange_line(i, j))
  3. 返回 SUM(i = 0, i < points.length, points(i).outcome_payout * lagrange(i))

双曲线曲线片段

本规范的目标是高效、紧凑地实现一般的双曲线形状,同时也确保更简单、更常见的赔付曲线(例如constant/outcome)在符合通用结构的同时不会变得复杂。

这是通过总共使用 7 个参数来完成的,这些参数可以(几乎)唯一地表达曲线 1/x 的每个仿射变换。 具体来说,我们有 (f_1, f_2) + ((a, b), (c, d))*(x, 1/x),它是点 (f_1, f_2) 的某种平移,添加到由矩阵 ((a, b), (c, d)) 变换的曲线 (x, 1/x),其中 a*d =/= b*c。 最后一个参数是一个布尔值,用于在存在歧义时指定要使用的曲线片段。

这个方案使简单曲线保持简单,为了表示任何形式为 constant/x + constant' 的曲线,我们只需 设置 f_1 = b = c = 0, a = 1, d = constant, f_2 = constant'

双曲线序列化
1. 实现:`payout_curve_piece`
2. 类型:1
3. 数据:
   * [`bool`:`use_positive_piece`]
   * [`bool`:`translate_outcome_sign`]
   * [`u64`:`translate_outcome`]
   * [`u16`:`translate_outcome_extra_precision`]
   * [`bool`:`translate_payout_sign`]
   * [`u64`:`translate_payout`]
   * [`u16`:`translate_payout_extra_precision`]
   * [`bool`:`a_sign`]
   * [`u64`:`a`]
   * [`u16`:`a_extra_precision`]
   * [`bool`:`b_sign`]
   * [`u64`:`b`]
   * [`u16`:`b_extra_precision`]
   * [`bool`:`c_sign`]
   * [`u64`:`c`]
   * [`u16`:`c_extra_precision`]
   * [`bool`:`d_sign`]
   * [`u64`:`d`]
   * [`u16`:`d_extra_precision`]

如果将 use_positive_piece 设置为 true,则使用 y_1,否则使用 y_2

然后有六个数值,表示为一个 bool 符号(对于正数设置为 true,对于负数设置为 false)、一个 u64 整数和一个 u16 额外精度。 准确地说,使用的数字应该是:(num_sign)( num + double(num_extra_precision) >> 16)。

字段 translate_outcometranslate_payout 分别对应于值 f_1f_2

请注意,此 payout_function 是从提供者的角度来看的。 要评估接受者的 payout_function,你必须在给定的 event_outcome 处评估提供者的 payout_function,并从 total_collateral 中减去结果赔付。

要求
  • a*d 必须不等于 b*c
  • 结果曲线必须为每个 event_outcome 定义(没有零除)。
双曲线评估

双曲线的两个曲线片段可以通过一个布尔标志选择出来,这两个曲线片段都仅仅是对以上表达式中 y坐标的参数化,当它的表达式展开后:

y_1 = c * (x - f_1 + sqrt((x - f_1)^2 - 4*a*b))/(2*a) + 2*a*d/(x - f_1 + sqrt((x - f_1)^2 - 4*a*b)) + f_2

y_2 = c * (x - f_1 - sqrt((x - f_1)^2 - 4*a*b))/(2*a) + 2*a*d/(x - f_1 - sqrt((x - f_1)^2 - 4*a*b)) + f_2

我们将 y_1 称为正片段,将 y_2 称为负片段,仅仅因为它们分别使用正平方根 和负平方根。

作者

Nadav Kohen <nadavk25@gmail.com>

知识共享许可协议 <br> 本作品已获得 知识共享署名 4.0 国际许可协议 的许可。

  • 原文链接: github.com/discreetlogco...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
discreetlogcontracts
discreetlogcontracts
江湖只有他的大名,没有他的介绍。