您正在查看: Other Chain 分类下的文章

零知识证明:STARKs 与 SNARKs

新技术之间的冲突

纵观历史,总是有类似的技术大约在同一时间进入市场,寻求类似的结果,但以不同的方式解决问题。当这种市场现象发生时,采用者应该尝试客观地评估每一项技术。

由于 STARK 阵营和 SNARK 阵营都对各自的技术充满热情,我们认为对这两种技术进行客观比较会很有趣。

STARKs 与 SNARKs

快速复习一下,零知识证明技术使一方能够向另一方证明他们知道某些事情,而证明者不必自己传达信息来证明他们的知识。它们既是一种隐私增强技术,因为它们减少了用户之间需要提供的信息量,又是一种扩展技术,因为它们可以允许以更快的速度验证证明,因为它们不包含全部信息非私有系统的信息。

当今市场上最引人注目的两种零知识技术是 zk-STARKs 和 zk-SNARKs。两者都是双方证明知识的方法的首字母缩写词:zk-STARK代表零知识可扩展的透明知识论证,zk-SNARK代表零知识简洁非交互式知识论证。本文将深入探讨从文化和技术的角度分析这两种不同的零知识技术之间的核心差异。此外,这两种零知识技术本质上都是非交互式的,这意味着代码可以部署并自主运行。

下面,我们有几个表格描述了两种技术之间的一些高级差异。我们还将深入研究段落格式的差异。


资料来源:物质实验室

SNARK

2012 年 1 月,加州大学伯克利分校一位名叫 Alessandro Chiesa 的教授与人合着了一篇论文,为他们首次构建的零知识证明创造了术语 zk-SNARK。Zk-SNARK 在其基础上依赖于椭圆曲线来保证安全性。密码学中的椭圆曲线在基本假设下运行,即找到相对于公知基点的随机椭圆曲线元素的离散对数是不可行的。

虽然关于椭圆曲线随机数生成器是否存在后门一直存在很大争议,但整个算法通常仍然是安全的。尽管侧信道攻击中有几个流行的漏洞,但可以通过多种技术轻松缓解。量子攻击确实笼罩在基于椭圆曲线的密码学上,但打破其安全模型所需的量子计算尚未广泛应用。

除了基于椭圆曲线之外,zk-SNARK 还需要可信设置。可信设置是指密钥的初始创建事件,用于创建私人交易所需的证明以及这些证明的验证。最初,当这些密钥被创建时,在验证密钥和发送私人交易的密钥之间有一个隐藏的参数链接。如果用于在可信设置事件中创建这些密钥的秘密没有被破坏,则可以利用这些秘密通过虚假验证来伪造交易,从而使持有者能够执行诸如凭空创建新令牌并使用它们等操作用于交易。由于 zk-SNARKs 的隐私特性,将无法验证凭空创建的代币实际上是凭空创造。话虽如此,信任设置仅在最初需要

因此,基于 SNARK 的网络的用户必须依赖于正确执行可信设置的事实,这意味着与可信设置密钥相关的秘密已被销毁,并且不会由监督仪式的个人持有。对可信设置的依赖一直是 SNARK 批评者最关注的领域之一。话虽如此,开发人员只需要最初使用可信设置,而不是持续使用。

SNARK 的另一个重要批评领域是它们不具有量子抗性。一旦量子计算在很大程度上可用,SNARKs 背后的隐私技术将被打破。当然,SNARKs 的支持者正确地指出,当使用量子计算机时,我们将面临更多问题,例如破坏 RSA 和大多数钱包基础设施。

话虽如此,尽管存在与可信设置相关的问题,但实际上 SNARK 的采用速度比 STARK 快得多的原因有很多。SNARKs 比 STARKs 早几年被发现,这使该技术在采用方面取得了重要的领先优势。Zcash 是较早的数字资产项目之一,它在区块链开发社区中普及了 SNARK 的使用。由于 Zcash 和其他 SNARKs 的采用者,SNARKs 拥有最多的开发人员库、已发布的代码、项目和积极致力于该技术的开发人员。除了 Zcash,新兴的 DEX Loopring 也使用了 SNARK。如果开发人员想开始使用零知识技术,他们在使用 SNARK 方面将比 STARK 获得更多支持。

此外,据估计 SNARK仅需要 STARK 所需气体的 24%,这意味着与 SNARK 进行交易对最终用户来说要便宜得多。最后,SNARKs 的证明大小比 STARKs 小得多,这意味着它需要更少的链上存储。

STARKs

虽然 SNARKs 在文档和开发人员支持方面比 STARKs 有一些明显的优势,但 STARKs 确实提供了一些独特的好处。但首先,让我们从技术角度深入了解 STARK 是什么。

Eli Ben-Sasson、Iddo Bentov、Yinon Horeshy 和 Michael Riabzev 在2018 年撰写了第一篇描述 STARK 的论文。与 SNARK 不同,STARK 的基础技术依赖于哈希函数。马上,依靠散列函数提供了一些好处,例如抗量子。此外,开始在网络中使用 STARK 不需要可信设置。

话虽如此,STARKs 的证明尺寸远大于 SNARKs,这意味着验证 STARKs 比 SNARKs 花费更多的时间,并且也导致 STARKs 需要更多的 gas。

此外,由于缺乏开发人员文档和社区,开发人员将很难使用 STARK。虽然有一些项目创建了基于 STARK 的扩展解决方案,例如STARKWARE,但 SNARKs 社区仍然要大得多。

虽然两个开发者社区都支持 SNARKs 和 STARKs,但以太坊基金会特别表达了对利用 Starks 的 STARKware 的声音支持。事实上,以太坊基金会向 STARKware 提供了 1200 万美元的赠款,这清楚地表明了他们对新兴技术的投入。

此外,虽然与 SNARK 相比,STARK 的文档相形见绌,但技术社区最近为那些希望实施尖端技术的人开发了更多资源

感谢 Anish Mohammad 的洞察力和 专业知识。

原文:https://consensys.net/blog/blockchain-explained/zero-knowledge-proofs-starks-vs-snarks/

什么是 zk-SNARK?

Zcash 是 zk-SNARKs 的第一个广泛应用,zk-SNARKs 是一种新颖的零知识密码学形式。Zcash 强大的隐私保证源于这样一个事实,即 Zcash 中的屏蔽交易可以在区块链上完全加密,但仍然可以通过使用 zk-SNARK 证明在网络的共识规则下验证其有效性。

首字母缩略词 zk-SNARK 代表“零知识简洁的非交互式知识论证”,指的是一种证明结构,在这种结构中,人们可以证明拥有某些信息,例如一个秘密密钥,而无需透露该信息,并且之间没有任何交互证明者和验证者。

“零知识”证明允许一方(证明者)向另一方(验证者)证明一个陈述是真实的,而不会透露超出陈述本身有效性的任何信息。例如,给定一个随机数的散列,证明者可以说服验证者确实存在一个具有该散列值的数字,而无需透露它是什么。

在零知识“知识证明”中,证明者不仅可以说服验证者该数字存在,而且他们实际上知道这样一个数字——同样,无需透露有关该数字的任何信息。“Proof”和“Argument”之间的区别是非常技术性的,我们不在这里讨论。

“简洁”的零知识证明可以在几毫秒内得到验证,即使是关于非常大的程序的语句,证明长度也只有几百字节。在第一个零知识协议中,证明者和验证者必须来回通信多轮,但在“非交互式”结构中,证明由从证明者发送到验证者的单个消息组成。目前,生成非交互式且足够短以发布到区块链的零知识证明的最有效的已知方法是具有初始设置阶段,该阶段生成在证明者和验证者之间共享的公共参考字符串。我们将这个公共引用字符串称为系统的公共参数。

如果有人可以访问用于生成这些参数的秘密随机性,他们将能够创建对验证者来说看起来有效的虚假证明。对于 Zcash,这意味着恶意方可以制造假币。为了防止这种情况发生,Zcash 通过精心设计的多方仪式生成了公共参数。要了解有关我们的参数生成仪式的更多信息,并查看我们为防止暴露 Zcash 所必需的秘密随机性(例如,计算机被喷灯)而采取的预防措施,请访问我们的Paramgen 页面。要了解有关参数生成协议背后的数学原理的更多信息,请阅读我们关于该主题的博客文章或白皮书 ( 1 , 2 )。

如何在 Zcash 中构建 zk-SNARK

为了在 Zcash 中实现零知识隐私,根据网络的共识规则确定交易有效性的函数必须返回交易是否有效的答案,而不会泄露其执行计算的任何信息。这是通过在 zk-SNARKs 中编码一些网络的共识规则来完成的。在高层次上,zk-SNARK 的工作原理是首先将您想要证明的内容转换为关于了解某些代数方程的解的等效形式。在下一节中,我们简要概述了如何将确定有效交易的规则转换为方程,然后可以对候选解决方案进行评估,而无需向验证方程的各方透露任何敏感信息。

Computation → Arithmetic Circuit → R1CS → QAP → zk-SNARK

将我们的交易有效性函数转化为数学表示的第一步是将逻辑步骤分解为尽可能小的操作,从而创建一个“算术电路”。类似于布尔电路,其中程序被编译成离散的单步,如 AND、OR、NOT,当程序被转换为算术电路时,它被分解为由加法、减法等基本算术运算组成的单步,乘法和除法(尽管在我们的特殊情况下,我们将避免使用除法)。

以下是用于计算表达式 (a+b)(bc) 的算术电路的示例:

看看这样的电路,我们可以将输入值 a、b、c 视为在电线上从左到右“行进”到输出电线。我们的下一步是构建所谓的 1 级约束系统或 R1CS,以检查值是否“正确移动”。在这个例子中,R1CS 将确认,例如,从 b 和 c 进入的乘法门出来的值是 b*c。

在这个 R1CS 表示中,验证器必须检查许多约束——几乎电路的每条线路都有一个约束。(由于技术原因,事实证明我们只对来自乘法门的电线有约束。)在 2012 年关于该主题的论文中,Gennaro、Gentry、Parno 和 Raykova 提出了一种“将所有这些约束捆绑在一起”的好方法. 此方法使用称为二次算术程序 (QAP) 的电路表示。需要检查的单个约束现在在多项式之间而不是在数字之间。多项式可能非常大,但这没关系,因为当多项式之间不存在同一性时,它在大多数情况下都不会成立。因此,您只需检查两个多项式是否在随机选择的一个点上匹配 以便以高概率正确验证证明。

如果证明者事先知道验证者会选择检查哪一点,他们可能能够制作无效的多项式,但仍然满足该点的身份。与ZK-SNARKs,复杂的数学技术,如同态加密配对的椭圆曲线的用于评价多项式“盲目地” -即在不知道哪个点正被评估。上面描述的公共参数用于确定将检查哪个点,但采用加密形式,以便证明者和验证者都不知道它是什么。

到目前为止的描述主要解决了如何获得“SNARKs”中的 S 和 N——如何获得一个简短的、非交互式的、单消息证明——但没有解决“zk”(零知识)部分,它允许证明者维护其秘密输入的机密性。事实证明,在这个阶段,通过让证明者使用仍然满足所需身份的原始多项式的“随机移位”,可以轻松添加“zk”部分。

有关 Zcash 中 zk-SNARKs 背后关键概念的分步深入解释,请参阅我们的 SNARKs 解释器系列,其中包含以下帖子:

  1. 同态隐藏
  2. 多项式的盲评估
  3. 系数检验与假设的知识
  4. 如何使多项式的盲评估可验证
  5. 从计算到多项式
  6. 匹诺曹协议
  7. 椭圆曲线的配对

Zcash 使用bellman,这是一个用于 zk-SNARK 的 Rust 语言库。在 Sapling 升级之前,Zcash 使用了 C++ 库libsnark 的一个分支。要更深入地了解用于 Zcash 的 zk-SNARK 的协议,请参阅有关Pinocchio 协议的论文,该协议一直使用到 Sapling 升级,以及目前使用的Jens Groth 的 zk-SNARK。

如何应用 zk-SNARKs 来创建屏蔽交易

在比特币中,交易通过将发送者地址、接收者地址以及公共区块链上的输入和输出值链接起来来验证。Zcash 使用 zk-SNARKs 来证明有效交易的条件已得到满足,而无需透露有关所涉及的地址或价值的任何关键信息。屏蔽交易的发送者构建了一个证明来证明,很有可能:

  • 输入值总和为每个屏蔽传输的输出值。
  • 发件人证明他们拥有输入票据的私人消费密钥,从而赋予他们消费的权力。
  • 输入票据的私人支出密钥以加密方式链接到整个交易的签名,这样交易就不能被不知道这些私人密钥的一方修改。

此外,屏蔽交易必须满足下面描述的一些其他条件。
比特币跟踪未花费的交易输出 (UTXO) 以确定哪些交易是可以花费的。在 Zcash 中,UTXO 的屏蔽等价物称为“承诺”,花费承诺涉及揭示“无效者”。Zcash 节点保存所有已创建承诺的列表,以及已披露的所有无效者。承诺和无效符存储为哈希,以避免披露有关承诺的任何信息,或哪些无效符与哪些承诺相关。
对于由屏蔽支付创建的每个新票据,都会发布一个承诺,其中包含以下各项的散列:票据发送到的地址、发送的金额、该票据唯一的数字“rho”(后来用于派生无效符)和一个随机随机数。

Commitment = HASH(收件人地址,金额,rho,r)

当一个受保护的交易被花费时,发送者使用他们的花费密钥来发布一个无效符,它是来自尚未花费的现有承诺的秘密唯一编号(“rho”)的散列,并提供零知识证明证明他们被授权消费。该散列不能已经在跟踪区块链中每个节点保存的已用交易的无效符集合中。

Nullifier = HASH(支出密钥,rho)

屏蔽交易的零知识证明验证了,除了上面列出的条件外,以下断言也是正确的:

  • 对于每个输入注释,都存在显露的承诺。
  • 无效符和票据承诺计算正确。
  • 输出音符的无效符与任何其他音符的​​无效符发生冲突是不可行的。

除了用于控制地址的支出密钥之外,Zcash 还使用一组证明和验证密钥来创建和检查证明。这些密钥在上面讨论的公共参数仪式中生成,并在 Zcash 网络中的所有参与者之间共享。对于每个受保护的交易,发送方使用他们的证明密钥来生成他们输入有效的证明。矿工通过使用验证密钥检查证明者的计算来检查受保护的交易是否遵循共识规则。Zcash 的证明生成方式需要证明者预先做更多的工作,但它简化了验证,从而将主要的计算工作卸载给交易的创建者(这就是为什么创建屏蔽的 Zcash 交易可能需要几个秒,

Zcash 的屏蔽交易的隐私依赖于标准的、久经考验的密码学(散列函数和流密码),但它是 zk-SNARKs 的添加,与承诺和无效系统一起应用,允许屏蔽交易的发送者和接收者证明加密交易是有效的。为加密货币提供隐私的其他方法依赖于模糊交易之间的联系,但 Zcash 交易可以存储在完全加密的区块链上这一事实为加密货币应用开辟了新的可能性. 加密交易允许各方享受公共区块链的好处,同时仍然保护他们的隐私。计划中的未来升级将允许用户自行决定有选择地披露有关屏蔽交易的信息。有关Zcash 的未来计划,请参阅我们的 Zcash 近期博客文章。

有关如何在 Zcash 中构建屏蔽交易的更深入解释,请参阅我们关于屏蔽地址之间的交易如何工作的博客文章。有关当前 Zcash 协议的完整详细信息,请参阅我们的协议规范

zk-SNARKs 的未来应用

在 Zcash 中创建屏蔽交易只是 zk-SNARK 的许多可能应用中的一个例子。理论上,您可以使用 zk-SNARK 来验证任何关系,而不会泄露输入或泄漏信息。为复杂函数生成证明仍然是计算密集型的,对于许多应用程序来说不实用,但Zcash 团队正在推动优化 zk-SNARKs 的边界,并且已经通过更有效的实现开辟了新天地。

就目前而言,Zcash 的 zk-SNARK 实现可以添加到任何现有的分布式账本解决方案中,作为企业用例的零知识安全层Zcash 团队的科学家是世界上最博学的 zk-SNARKs 研究人员之一,并不断致力于提出新的应用程序并提高零知识协议的效率。如果你的业务需要,可以从零知识证明或具有强大的隐私blockchain应用的解决方案中受益,取得联系我们的业务发展团队。

原文:https://z.cash/technology/zksnarks/

Swarm激励模型合约

源代码:https://github.com/ethersphere/swap-swear-and-swindle

Swarm目前只跑在Goerli测试网,核心的合约为ERC20SimpleSwap,源代码在master分支,对于后续功能合约 full Swap, Swear and Swindle相关代码在experimental分支

逻辑简述

目前Swarm的Goerli测试网部署的合约逻辑为
用户通过执行SimpleSwapFactory合约,部署ERC20SimpleSwap合约,并与发行的gBZZ代币交互

用户与Swarm交互

测试用户

0x10210572d6b4924Af7Ef946136295e9b209E1FA0

部署ERC20SimpleSwap合约

用户地址通过执行SimpleSwapFactory合约的deploySimpleSwap方法,完成部署ERC20SimpleSwap合约,通过查看交易的eventlog,部署的Chequebook合约地址为0x68f7ea9f3fae55e3e55fee0f0761b7df570fd212

deploySimpleSwap合约代码

function deploySimpleSwap(address issuer, uint defaultHardDepositTimeoutDuration)
  public returns (address) {
    address contractAddress = address(new ERC20SimpleSwap(issuer, ERC20Address, defaultHardDepositTimeoutDuration));
    deployedContracts[contractAddress] = true;
    emit SimpleSwapDeployed(contractAddress);
    return contractAddress;
  }

deploySimpleSwap执行同时完成了ERC20SimpleSwap的构造初始化

constructor(address _issuer, address _token, uint _defaultHardDepositTimeout) public {
    issuer = _issuer;
    token = ERC20(_token);
    defaultHardDepositTimeout = _defaultHardDepositTimeout;
  }

同时初始化了交互Token gBZZ代币

订金(为支票薄提供资金)

通过执行API进行为支票薄提供资金
http://localhost:1635/chequebook/deposit?amount=1000000000
执行的逻辑为用户地址0x10210572d6b4924af7ef946136295e9b209e1fa0向自己的Chequebook合约地址0x68f7ea9f3fae55e3e55fee0f0761b7df570fd212转对应数量的gBZZ,也可以理解成充值抵押

测试交易

https://goerli.etherscan.io/tx/0x81080bdc2ee4ede8898f003c4b74c9dcabdd6467e443f9ab8669f0c62434aaee

提现

将自己Chequebook合约中的gBZZ体现到用户地址
执行逻辑为执行自己的Chequebook合约地址0x68f7ea9f3fae55e3e55fee0f0761b7df570fd212中的withdraw方法。

合约代码

function withdraw(uint amount) public {
    /* 只有发行人可以做到这一点 */
    require(msg.sender == issuer, "not issuer");
    /* 确保我们不从保证金中提取任何东西 */
    require(amount <= liquidBalance(), "liquidBalance not sufficient");
    require(token.transfer(issuer, amount), "transfer failed");
  }

测试交易

https://goerli.etherscan.io/tx/0x967fabac2da76d868a9f80d8a64baacbe2cb585519388a4494593705ef050421

分析ERC20SimpleSwap合约

// SPDX-License-Identifier: BSD-3-Clause
pragma solidity =0.7.6;
pragma abicoder v2;
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/math/Math.sol";
import "@openzeppelin/contracts/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/presets/ERC20PresetMinterPauser.sol";


/**
@title支票簿合同,无豁免
@author The Swarm作者
@notice支票簿合同允许支票簿的发行者将支票发送到无限数量的交易对手。
此外,可以通过hardDeposits保证偿付能力
@dev作为发行人,如果发送的支票的累积价值高于所有存款的累积价值,则不应该发送任何支票
作为受益人,我们应始终考虑支票弹跳的可能性(当未分配hardDeposits时)
*/
contract ERC20SimpleSwap {
  using SafeMath for uint;

  event ChequeCashed(
    address indexed beneficiary,
    address indexed recipient,
    address indexed caller,
    uint totalPayout,
    uint cumulativePayout,
    uint callerPayout
  );
  event ChequeBounced();
  event HardDepositAmountChanged(address indexed beneficiary, uint amount);
  event HardDepositDecreasePrepared(address indexed beneficiary, uint decreaseAmount);
  event HardDepositTimeoutChanged(address indexed beneficiary, uint timeout);
  event Withdraw(uint amount);

  uint public defaultHardDepositTimeout;
  /* 跟踪每个受益人的硬存款(偿付能力的链上保证)的结构*/
  struct HardDeposit {
    uint amount; /* 分配的硬存款金额 */
    uint decreaseAmount; /* 减少量从请求减少时的金额中减去 */
    uint timeout; /* 发行者必须等待超时秒数才能减少hardDeposit,0表示应用defaultHardDepositTimeout */
    uint canBeDecreasedAt; /* 可以减少硬质沉积的时间点 */
  }

  struct EIP712Domain {
    string name;
    string version;
    uint256 chainId;
  }

  bytes32 public constant EIP712DOMAIN_TYPEHASH = keccak256(
    "EIP712Domain(string name,string version,uint256 chainId)"
  );
  bytes32 public constant CHEQUE_TYPEHASH = keccak256(
    "Cheque(address chequebook,address beneficiary,uint256 cumulativePayout)"
  );
  bytes32 public constant CASHOUT_TYPEHASH = keccak256(
    "Cashout(address chequebook,address sender,uint256 requestPayout,address recipient,uint256 callerPayout)"
  );
  bytes32 public constant CUSTOMDECREASETIMEOUT_TYPEHASH = keccak256(
    "CustomDecreaseTimeout(address chequebook,address beneficiary,uint256 decreaseTimeout)"
  );

  // 该合同使用的EIP712域
  function domain() internal pure returns (EIP712Domain memory) {
    uint256 chainId;
    assembly {
      chainId := chainid()
    }
    return EIP712Domain({
      name: "Chequebook",
      version: "1.0",
      chainId: chainId
    });
  }

  // 计算EIP712域分隔符。 这不能恒定,因为它取决于chainId
  function domainSeparator(EIP712Domain memory eip712Domain) internal pure returns (bytes32) {
    return keccak256(abi.encode(
        EIP712DOMAIN_TYPEHASH,
        keccak256(bytes(eip712Domain.name)),
        keccak256(bytes(eip712Domain.version)),
        eip712Domain.chainId
    ));
  }

  // 使用EIP712签名方案恢复签名
  function recoverEIP712(bytes32 hash, bytes memory sig) internal pure returns (address) {
    bytes32 digest = keccak256(abi.encodePacked(
        "\x19\x01",
        domainSeparator(domain()),
        hash
    ));
    return ECDSA.recover(digest, sig);
  }

  /* 此支票簿针对其写支票的令牌 */
  ERC20 public token;
  /* 将每个受益人与已支付给他们的款项相关联 */
  mapping (address => uint) public paidOut;
  /* 支付总额 */
  uint public totalPaidOut;
  /* 将每个受益人与他们的HardDeposit相关联 */
  mapping (address => HardDeposit) public hardDeposits;
  /* 所有硬存款的总和 */
  uint public totalHardDeposit;
  /* 合同的签发人,在施工中 */
  address public issuer;
  /* 指示支票是否在过去退回 */
  bool public bounced;

  /**
  @notice设置颁发者,令牌和defaultHardDepositTimeout。 只能被调用一次。
   @param _issuer此支票簿的支票的发行者(需要作为“将支票簿设置为付款”的参数)。
   _issuer必须是外部拥有的帐户,或者它必须支持调用函数cashCheque
   @param _token此支票簿使用的令牌
   @param _defaultHardDepositTimeout持续时间(以秒为单位),默认情况下将用于减少hardDeposit分配
  */
  function init(address _issuer, address _token, uint _defaultHardDepositTimeout) public {
    require(_issuer != address(0), "invalid issuer");
    require(issuer == address(0), "already initialized");
    issuer = _issuer;
    token = ERC20(_token);
    defaultHardDepositTimeout = _defaultHardDepositTimeout;
  }

  /// @return 支票簿的余额
  function balance() public view returns(uint) {
    return token.balanceOf(address(this));
  }
  /// @return 余额中没有硬存款支付的部分
  function liquidBalance() public view returns(uint) {
    return balance().sub(totalHardDeposit);
  }

  /// @return 余额中可用于特定受益人的部分
  function liquidBalanceFor(address beneficiary) public view returns(uint) {
    return liquidBalance().add(hardDeposits[beneficiary].amount);
  }
  /**
  @dev内部函数负责检查issuerSignature,更新hardDeposit余额并进行转账。
   由CashCheque和CashChequeBeneficary调用
   @param受益人支票分配给的受益人。 受益人必须是外部拥有的帐户
   @param接收者会收到累计付款与已付给受益人减去调用者的款项之间的差额
   @paramcumulativePayout分配给受益人的累计支票数量
   @param issuerSig如果发行人不是发件人,则发行人必须已在累计付款上明确授予受益人
  */
  function _cashChequeInternal(
    address beneficiary,
    address recipient,
    uint cumulativePayout,
    uint callerPayout,
    bytes memory issuerSig
  ) internal {
    /* 发行人必须通过作为主叫方或通过签名明确批准累计支付 */
    if (msg.sender != issuer) {
      require(issuer == recoverEIP712(chequeHash(address(this), beneficiary, cumulativePayout), issuerSig),
      "invalid issuer signature");
    }
    /* requestPayout是请求进行付款处理的金额 */
    uint requestPayout = cumulativePayout.sub(paidOut[beneficiary]);
    /* 计算实际支出 */
    uint totalPayout = Math.min(requestPayout, liquidBalanceFor(beneficiary));
    /* 计算硬存款使用量 */
    uint hardDepositUsage = Math.min(totalPayout, hardDeposits[beneficiary].amount);
    require(totalPayout >= callerPayout, "SimpleSwap: cannot pay caller");
    /* 如果有一些使用的硬存款,请更新hardDeposits */
    if (hardDepositUsage != 0) {
      hardDeposits[beneficiary].amount = hardDeposits[beneficiary].amount.sub(hardDepositUsage);

      totalHardDeposit = totalHardDeposit.sub(hardDepositUsage);
    }
    /* 增加存储的payedOut金额以避免双倍支付 */
    paidOut[beneficiary] = paidOut[beneficiary].add(totalPayout);
    totalPaidOut = totalPaidOut.add(totalPayout);

    /* 让全世界知道发行人对未兑现支票的承诺过高 */
    if (requestPayout != totalPayout) {
      bounced = true;
      emit ChequeBounced();
    }

    if (callerPayout != 0) {
    /* 如果指定,则转接到呼叫者 */
      require(token.transfer(msg.sender, callerPayout), "transfer failed");
      /* 进行实际付款 */
      require(token.transfer(recipient, totalPayout.sub(callerPayout)), "transfer failed");
    } else {
      /* 进行实际付款 */
      require(token.transfer(recipient, totalPayout), "transfer failed");
    }

    emit ChequeCashed(beneficiary, recipient, msg.sender, totalPayout, cumulativePayout, callerPayout);
  }
  /**
  @notice兑现非受益人的受益人支票,并通过发送方与发件人奖励这样做
  @dev受益人必须能够生成签名(作为外部拥有的帐户)才能使用此功能
  @param受益人支票分配给的受益人。受益人必须是外部拥有的帐户
  @param接收者会收到累计付款与已付给受益人减去调用者的款项之间的差额
  @paramcumulativePayout分配给受益人的累计支票数量
  @param受益人Sig受益人必须已经明确批准才能由发件人兑现累计付款并发送给callerPayout
  @param issuerSig如果发行人不是发件人,则发行人必须已在累计付款上明确授予受益人
  @param callerPayout当受益人还没有以太币时,他可以在callerPayout的帮助下激励其他人兑现支票
  @param issuerSig如果发行人不是发件人,则发行人必须已在累计付款上明确授予受益人
  */
  function cashCheque(
    address beneficiary,
    address recipient,
    uint cumulativePayout,
    bytes memory beneficiarySig,
    uint256 callerPayout,
    bytes memory issuerSig
  ) public {
    require(
      beneficiary == recoverEIP712(
        cashOutHash(
          address(this),
          msg.sender,
          cumulativePayout,
          recipient,
          callerPayout
        ), beneficiarySig
      ), "invalid beneficiary signature");
    _cashChequeInternal(beneficiary, recipient, cumulativePayout, callerPayout, issuerSig);
  }

  /**
  @注意兑现支票作为受益人
   @param接收者会收到累计付款与已付给受益人减去调用者的款项之间的差额
   @param的累计付款要求付款
   @param issuerSig发行者必须已对收款人的累计付款给予明确批准
  */
  function cashChequeBeneficiary(address recipient, uint cumulativePayout, bytes memory issuerSig) public {
    _cashChequeInternal(msg.sender, recipient, cumulativePayout, 0, issuerSig);
  }

  /**
  @注意准备减少硬沉积
   @dev减少hardDeposit必须分两步完成,以使受益人兑现任何未兑现的支票(并利用相关的硬存款)
   @param受益人,应减少其硬存款
   @param reduction存款应减少的金额
  */
  function prepareDecreaseHardDeposit(address beneficiary, uint decreaseAmount) public {
    require(msg.sender == issuer, "SimpleSwap: not issuer");
    HardDeposit storage hardDeposit = hardDeposits[beneficiary];
    /* 如果减少的幅度超过存款,则不能减少 */
    require(decreaseAmount <= hardDeposit.amount, "hard deposit not sufficient");
    // 如果从未设置hardDeposit.timeout,则应用defaultHardDepositTimeout
    uint timeout = hardDeposit.timeout == 0 ? defaultHardDepositTimeout : hardDeposit.timeout;
    hardDeposit.canBeDecreasedAt = block.timestamp + timeout;
    hardDeposit.decreaseAmount = decreaseAmount;
    emit HardDepositDecreasePrepared(beneficiary, decreaseAmount);
  }

  /**
  @notice自调用prepareDecreaseHard存款以来等待了必要的时间后,减少了硬存款
  @param受益人,应减少其硬存款
  */
  function decreaseHardDeposit(address beneficiary) public {
    HardDeposit storage hardDeposit = hardDeposits[beneficiary];
    require(block.timestamp >= hardDeposit.canBeDecreasedAt && hardDeposit.canBeDecreasedAt != 0, "deposit not yet timed out");
    /* this throws if decreaseAmount > amount */
    //TODO: 如果prepareDecreaseHardDeposit和reducedHardDeposit之间有现金兑现,那么reducingHardDeposit将抛出并且无法减少硬存款。
    hardDeposit.amount = hardDeposit.amount.sub(hardDeposit.decreaseAmount);
    /* 重置canBeDecreasedAt以避免两次减少 */
    hardDeposit.canBeDecreasedAt = 0;
    /* 保持totalDeposit同步 */
    totalHardDeposit = totalHardDeposit.sub(hardDeposit.decreaseAmount);
    emit HardDepositAmountChanged(beneficiary, hardDeposit.amount);
  }

  /**
  @注意增加硬保证金
   @param受益人,应减少其硬存款
   @param金额新的硬存款
  */
  function increaseHardDeposit(address beneficiary, uint amount) public {
    require(msg.sender == issuer, "SimpleSwap: not issuer");
    /* 确保保证金不超过全球余额 */
    require(totalHardDeposit.add(amount) <= balance(), "hard deposit exceeds balance");

    HardDeposit storage hardDeposit = hardDeposits[beneficiary];
    hardDeposit.amount = hardDeposit.amount.add(amount);
    // 我们没有明确设置hardDepositTimout,因为零意味着使用默认的HardDeposit超时
    totalHardDeposit = totalHardDeposit.add(amount);
    /* 禁用任何未决的减少 */
    hardDeposit.canBeDecreasedAt = 0;
    emit HardDepositAmountChanged(beneficiary, hardDeposit.amount);
  }

  /**
 @notice允许为每个受益人设置自定义的hardDepositDecreaseTimeout
   @dev,当必须保证偿付能力的时间长于defaultHardDepositDecreaseTimeout时,这是必需的
   @param收款人的硬存款减少的受益人必须更改超时
   @param hardDepositTimeout受益人新的hardDeposit.timeout
   @param受益人Sig受益人必须通过在新的reduceTimeout上签名来明确批准
  */
  function setCustomHardDepositTimeout(
    address beneficiary,
    uint hardDepositTimeout,
    bytes memory beneficiarySig
  ) public {
    require(msg.sender == issuer, "not issuer");
    require(
      beneficiary == recoverEIP712(customDecreaseTimeoutHash(address(this), beneficiary, hardDepositTimeout), beneficiarySig),
      "invalid beneficiary signature"
    );
    hardDeposits[beneficiary].timeout = hardDepositTimeout;
    emit HardDepositTimeoutChanged(beneficiary, hardDepositTimeout);
  }

  /// @notice提取以太币
   /// @param要提取的金额
   // solhint-disable-next-line no-simple-event-func-name
  function withdraw(uint amount) public {
    /* 只有发行人可以做到这一点 */
    require(msg.sender == issuer, "not issuer");
    /* 确保我们不从保证金中提取任何东西 */
    require(amount <= liquidBalance(), "liquidBalance not sufficient");
    require(token.transfer(issuer, amount), "transfer failed");
  }

  function chequeHash(address chequebook, address beneficiary, uint cumulativePayout)
  internal pure returns (bytes32) {
    return keccak256(abi.encode(
      CHEQUE_TYPEHASH,
      chequebook,
      beneficiary,
      cumulativePayout
    ));
  }  

  function cashOutHash(address chequebook, address sender, uint requestPayout, address recipient, uint callerPayout)
  internal pure returns (bytes32) {
    return keccak256(abi.encode(
      CASHOUT_TYPEHASH,
      chequebook,
      sender,
      requestPayout,
      recipient,
      callerPayout
    ));
  }

  function customDecreaseTimeoutHash(address chequebook, address beneficiary, uint decreaseTimeout)
  internal pure returns (bytes32) {
    return keccak256(abi.encode(
      CUSTOMDECREASETIMEOUT_TYPEHASH,
      chequebook,
      beneficiary,
      decreaseTimeout
    ));
  }
}

Cash Cheque Beneficiary

https://goerli.etherscan.io/tx/0x6e255bcd2eb2a84d2a80e25368413f805a036d3cea18d43c81e96f0158a3084c