Hyperledger Indy分析报告

Hyperledger Indy提供了一种基于区块链或其他分布式账本的分布式数字身份(DID)方案,这种方案中包含了各种工具、库和可重用工具,以便于这些数字身份可在多个管理域、应用程序以及其他任何数据孤岛上互通。

一、Indy架构综述

从横向看,Indy主要分为项目分布、生态系统架构、数据模型(DID、DID Document、Verified Credentials);从纵向看,Indy主要分为业务层(Business Layer)、应用层(Applications Layer)和技术层(Technology Layer)。

总体架构如下图所示:

HBB-Indy-Sovrin ARM v0.27

1. 业务层

image-20190704181446731

业务层主要使用以下几个应用层提供的功能服务:凭证发行(Issue)、凭证存储(Local Store)、数据请求(Request)、凭证披露(Present)、凭证验证(Verify)、凭证撤销(Revocate)用户注册登录等。

2. 应用层

应用层主要功能:提供DID服务、提供凭证服务、提供特定业务服务。

image-20190704181354240

应用层由Indy底下 Indy-AgentIndy-SDK 两个项目支撑。Indy-SDK项目提供钱包功能组件(Wallet Component)支撑数据存储与开发,使用技术层Agent API提供的服务;Indy-Agent项目提供代理功能组件(Agent Component)支撑业务开发。

3. 技术层

image-20190704181540967

技术层也可再分为Cloud Agent层和Ledger层。

Cloud Agent层由Indy-Agent项目、Indy-SDK项目和 DIF Universal-Resolver 项目作支撑,主要功能是提供对Edge Agent的服务,直接与Ledger层进行交互,将请求结果返回到应用层。Indy-SDK项目调用Ledger层的API,实现直接与区块链进行交互;Indy-Agent项目调用Indy-SDK的钱包API,提供代理功能组件,并提供凭证注册组件(Credential Registry Node Component)实现凭证的注册;DIF Universal-Resolver项目提供DID的解析器组件,直接与Ledger层交互,解析出DID。

Ledger层由 Indy-NodeIndy-Plenum 两个项目实现,主要功能是实现Indy区块链的功能。Indy-Plenum

项目主要实现的是Plenum BFT共识协议,是Fabric for Identity的定制版,是整个Indy项目的核心部分;Indy-Node是在Indy-Plenum区块链之上的服务器,直接与区块链层进行交互,实现节点功能。

4. 数据模型

DID、DID Document都是按照W3C所定的数据标准建立的,主要关系如下图所示:

image-20190704171205582

二、Hyperledger Indy工作流

以一个Alice从Faber大学毕业找工作的场景简单介绍Hyperledger Indy的工作流。(链接

Step 1: 政府生成凭证模板(Schema)

img

政府颁发Transcript Schema和Job-Certificate Schema,并将其记录到分布式账本中。每个人都可以访问这些模板。

Step 2: Faber大学和Acme公司生成他们自己的凭证定义(Credential definition)

Faber大学和Acme公司根据政府发布的模板创建自己的凭证的定义,并将定义上传到链上。

img
img

Alice从Faber大学获得成绩单(凭证),然后再申请工作时想Acme公司提供证书作为教育资格证明。

Step 3: Alice从Faber大学获得成绩单凭证

img

具体子步骤如下:

  • Alice与Faber大学建立连接;
  • Faber 大学生成Credential offer给Alice;
  • Alice从分布式账本中查询Faber大学对成绩单的定义,生成Credential request发送给Faber大学;
  • Faber大学生成一个Credential给Alice,这个Credential内含了足够的proof证明凭证的有效性;
  • Alice收到Credential,保存到自己的钱包(wallet)。

Step 4: Acme公司请求Alice的教育证明

img

具体子步骤如下:

  • Acme公司与Alice建立连接;
  • Acme生成proof request发送给Alice,这个请求中包含了应该符合的条件;
  • Alice收到了proof request,基于Credential生成对应的proof,发送给Acme公司;
  • Acme公司收到了proof,并验证其有效性;
  • Acme公司确认了proof。

之后Alice获得了Acme的工作凭证(job-certificate),并将该凭证发送给Thrift银行以证明聘期,便可申请贷款。(Alice使用Faber大学给的教育凭证完成了KYC过程)

Step5: Alice获得了Acme公司给的工作证明

img

具体子步骤如下:

  • Acme公司发送Credential Offer给Alice;
  • Alice从分布式账本获取Acme Credential的定义,生成Credential request发送给Acme公司;
  • Acme公司生成Credential发送给Alice,该凭证中包含了足够多的证明以证明Alice的聘期;
  • Alice收到Credential并保存到钱包(wallet)里。

Step 6 : Thrift银行请求Alice的聘期证明,以及Alice 的KYC

img

具体子步骤如下:

  • Thrift银行与Alice建立连接;
  • Thrift银行生成两个proof request,发送给Alice;在这里的例子中,银行要求工资大于2000,工作经验大于1年,以及KYC流程;
  • Alice收到两个proof request,基于Faber大学的凭证以及Acme公司的凭证收成两个proof,发送给Thrift银行;
  • Thrift银行收到了证明,并确认其有效性;
  • Thrift银行确认了Alice的proof。

三、关键技术点

Indy整个项目的文件分布如下:

  • 分布式账本

    • Indy-Node:实现在基于Plenum BFT的分布式账本功能与节点功能,;
    • Indy-Plenum:实现基于Plenum BFT共识机制,定义wallet基类;
  • 客户端工具

    • Indy-SDK:由两个主要组件构成:libindy(底层),libVCX(实现层);
    • Indy-Agent:项目实现Agent Client,有以下几种功能方向的App:
      • Edge Agent App;
      • Edge Agent Web App;
      • Edge Agent Lightweight App;
      • Cloud Agent Node;
      • Ledger Node;
      • Credential Registry Agent Mode;
  • 通用组件:

    Indy-Crypto:承用了Hyperledger URSA,作为Indy的密码学代码库;

    Indy-HIPE(Hyperledger Indy Project Enhancements):实现了Indy的功能增强组件。

接下来将按照项目文件的主体部分进行逐层解析。

1. Indy-Plenum

Indy-Plenum.png

从内容上看,Plenum项目主要实现了基于RBFT改进的共识机制以及共识操作方法,定义了四种链分别存储整个Indy的配置、事务等信息,定义了DID的wallet以及DID签名验证方法。项目主要分为以下几个部分:

1.1 plenum包

plenum包实现了基于 RBFT 改进的Plenum BFT共识算法,在Commit阶段手机n-of-l个的BLS签名,在共识Order阶段,对多个BLS签名进行聚合。(:BLS签名算法在Indy-Crypto中详细说明)

Plenum共识改进部分大致在以下三点体现:

  • RBFT每个请求都会发起三段commit,Plenum是一堆请求才发起三段commit;
  • Plenum在PRE-PREPARE和PREPARE阶段都保存了默克尔树根与state树根;
  • 三段commit都是用了BLS多签名聚合方法,客户端不需要其他节点响应。

可以通过这个链接看到Plenum共识协议流程图:Plenum Diagrams

此外,plenum包定义了DID的钱包(wallet)基类,以及DID对消息签名、签名验证的方法。

钱包类主要数据结构如下:

  • name -> str:id名;
  • ids -> Dict[Identifier, IdData]:DID到IdData的映射,一个DID一个IdData;
  • idsToSigners -> Dict[Identifier, Signer]:DID到Signer的映射,一个DID一个Signer;
  • aliasesToIds -> Dict[Alias, Identifier]:(别名)Alias到DID的映射,一个DID对应多个Alias。

钱包类主要方法:

  • Encrypt/Decrypt:均使用 libsodium密码库的python封装进行加解密;
  • sign/verify:签名和签名验证方法都是基于libsodium的sign方法进行的,使用signKey签名,使用veryKey+DID进行验证;
  • DID Create
    • 指定或产生Seed,32byte;
    • 根据Ed25519算法产生公私钥对 MSK,MPK;
    • 再将产生的私钥作为种子Seed,使用Ed25519,生成新的公钥VerkeyRaw,新的签名私钥signKey;
    • 将verKeyRaw的前16字节进行base58解码,成为DID,后16字节为verKey。
    • 当signKey、verKey丢失时,将可以使用MSK再次重新生成。
  • Wallet storage:使用 jsonpickle 进行JSON序列化和反序列化,保存在本地。

1.2 ledger包

ledger包定义了整个区块链结构、创世块、Merkle tree结构以及实现了Merkle tree快速验证方法。

1.3 crypto包

crypto包是对Indy-Crypto项目部分功能的封装,主要实现了BLS多重签名。(注:BLS多重签名将在Indy-Crypto项目中详细说明)

1.4 存储组件

img

在Ledger组件中默认使用RocksDB和LevelDB进行KV存储,且使用Merkle Patricia Trie 存储账本状态。每个节点均维护四个账本:

  • Audit Ledger:主要负责与其他三个Ledger同步,为失败节点恢复数据,并对账本正确性进行审计;
  • Pool Ledger:主要维护池中节点的信息(Membership);
  • Domain Ledger:为主要账本,记录交易;
  • Config Ledger:是Pool节点的配置账本。

1.5 底层性能

目前暂未有数据文件表明Plenum的性能指标,还需进一步实际测试。

TODO: 测试Plenum性能指标。

2. Indy-Node

Indy-Node.png

Indy-Node项目主要实现了基于Plenum BFT共识协议的分布式账本,以及节点的功能。主要包括:处理读写请求、定义交易类型以及交易分类存储。

接下来将按照项目文件的主体部分进行逐层解析。

2.1 indy-node

Indy-node包主要实现基于Plenum BFT共识协议的分布式账本、对交易读写请求的处理以及提供了对特定交易的支持,各类交易都会记录到不同种类的Ledger中:

  • 存储到Domain Ledger的交易:
    • NYM:NYM交易主要实现创建新DID、对已存在的DID用户进行角色变更(注:角色变更涉及到权限管理,可查看交易权限管理部分的详情)DID Document就是对NYM交易进行溯源,即可得到一份完整的DID Document;
    • ATTRIB:主要实现对已有NYM添加一个属性;
    • SCHEMA:增加一个声明模板,模板主要包含:属性名模板名模板版本号
    • CLAIM_DEF:对一个声明进行定义,由Issuer创建并公开,若要对一个声明进行升级,需要用新的Issuer DID进行新的声明,主要包含:声明公钥声明撤销公钥签名类型(只支持CL签名算法)
  • 存储到Pool Ledger的交易:
    • NODE:在池中添加一个节点,或更新一个池中的节点;
  • 存储到Config Ledger的交易:
    • POOL_UPGRADE:由Trustee发起,升级Pool中的配置,也可更新池中特定的节点设置;
    • NODE_UPGRADE:记录节点升级后的节点状态,由升级的节点发出;
    • POOL_CONFIG:更改池中的配置。

2.2 indy-common

indy-common包主要定义了角色权限,以及一些对Indy-Plenum项目方法的封装。

2.3 dev-setup/evironment

dev-setup包和evironment包提供了节点部署方案,可以在本机部署(目前只支持Ubuntu、macOS),也可以部署在容器Docker上,可以部署在云端。

2.4 交易权限管理

Indy中节点可分为common usertrust anchorstewardtrustee 四类,每种特殊交易都有特定的角色权限才可以进行。具体可看链接

3. Indy-SDK

Indy-SDK.png

Indy-SDK作为Plenum分布式账本的SDK,主要是实现了代理端软件Agent可以与分布式账本、DID钱包以及DID doc进行互通,实现凭证Credential,为不同编程语言封装了主要功能。

3.1 libindy

如上图所示,libindy包主要面向应用提供了基础的区块构建方法,实现了对钱包Wallet、凭证Credential以及DID的操作,且提供不同编程语言调用的封装。并基于SDK提供了命令行工具CLI。

Credential

Indy Credential工作流图如下图所示: 工作流图

  1. Issuer生成凭证模板(Create Credential Schema): Issuer创建某个凭证Schema,并将Schema交易发送给Ledger;
  2. Issuer生成凭证定义(Create Credential Definition): Issuer想Ledger发送获取某个凭证Schema请求,得到Schema后创建凭证定义,存储公私钥对和正确性证明在本地,将该种凭证定义发送给Ledger;
  3. Issuer生成凭证撤销表(Create Revocation Registry): Issuer生成凭证撤销表,并将撤销表入口、撤销表定义发送到Ledger;
  4. Prover生成主密钥(Create Master secret): Prover生成主密钥,并保存在本地;
  5. Issuer生成凭证提议(Create Credential Offer): Issuer想要发行凭证给Prover,向wallet请求,wallet获取对应的正确性证明,再讲cred_offer消息发送给Prover;
  6. Prover请求,Issuer发行凭证(Request and Issue Credential): Prover收到cred_offer消息,向Ledger发送GET_CRED_DEF请求,填写好相应的表单之后生成凭证请求发送给Issuer;Issuer正式发行凭证给Prover,并向Ledger更新凭证撤销表入口;Prover获得凭证后向Ledger验证凭证有效,保存在本地;
  7. Prover展示凭证(Show Credential on UI): Prover向wallet请求,wallet返回对应Credential;
  8. Prover向Verifier展示凭证(Present Credential to 3rd Party(proof request)): Verifier发起证明请求,Prover给出相应的凭证,并向Ledger核实该凭证的撤消状态,确认之后便将凭证发送给Verifier;Verifier拿到凭证向Ledger核实;
  9. Issuer撤销凭证(Revoke Credential): Issuer获取密钥和撤销注册表,并向Ledger同步撤销注册表的入口。

(:Credential相关密码学部分将会在Indy-Crypto部分详细说明)

Command Line Interface

CLI组件设计如下图所示:

cli-components
Wallet

Wallet组件设计如下图所示:

Wallet Components

在Secrets API,允许调用密钥生成、DID生成等需要访问加密实体的方法;在Non-secrets API只允许访问存取wallet中特定的DID数据。Wallet API提供方法将钱包记录在本地SQL数据库中存取。

Decentrailized Key Management

相关文档并不多,只介绍了当密钥丢失时的措施:

  • 离线物理恢复密钥;
  • 需要trustee级别的用户进行恢复;
  • 考虑到安全性,恢复往往需要与密钥轮换和撤销相结合。

此外,在官方文档中提到了使用Shamir Secret Sharing Scheme是通过分布式密钥碎片存储,在密钥丢失时,只需要将碎片重新整合起来即可恢复。(:Shamir Secret Sharing Scheme会在Indy-Crypto中简单介绍)

3.2 libvcx

libvcx包实现了一种Agent-2-Agent的通信协议,用于credentials的交换。主要还是使用JSON进行通信,通信时使用SSL证书进行加密通信。(:Credential的选择性披露将在Indy-Crypto中详细说明)

4. Indy-Agent

Indy-Agent项目是一个基于Indy-SDK实现的代理客户端的合集,目前已有多种语言的客户端实现。

5. Indy-Crypto

Indy-Crypto项目是Hyperledger URSA密码库的一部分,主要在Indy项目使用的密码学方法有: BLS多签算法CL群签名算法 。其中,Indy使用BLS多签算法,减少Plenum共识算法的轮训,减少签名长度;Indy使用CL群签名算法对Credential进行选择性披露,实现零知识证明。

5.1

1). 准备阶段

有两个哈希方程 \(H_0: \{ 0, 1 \} ^* \rightarrow \Bbb{G}_2\)\(H_1: \{ 0, 1 \}^* \rightarrow \Bbb{Z}_q\)

$ H_0$是将元素哈希到 \(\Bbb{G}_2\) 群,\(H_1\)是将元素哈希到q阶整数群。

2). 参数生成

\(Pg(k): par \leftarrow (q, \Bbb{G}_1,\Bbb{G}_2, \Bbb{G}_t,e,g_1,g_2) \leftarrow G(k)\)

生成一个双线性配对群,其中q是阶数,$ _1, _2$ 是q阶群,\(e : \Bbb{G}_t \leftarrow \Bbb{G}_1 × \Bbb{G}_2\) 是一种映射关系,\(g_1,g_2\) 分别是 $ _1, _2$ 的生成子。

3). 密钥生成

\(Kg(par) : sk \xleftarrow{random} \Bbb{Z}_q ; pk \leftarrow g_2^{sk}\)

在整数群众随机抽取得出私钥 \(sk\) ,公钥 \(pk\) 由私钥计算得出。

4). 公钥聚合

\(KAg({pk_1,…,pk_n}) : apk \leftarrow \prod_{i=1}^n{pk_i^{H_1(pk_i, \{ pk_1,…,pk_n \} )}}\)

将所有的用户公钥都聚合起来,最终形成一个聚合公钥 \(apk\)

5). 签名

\(Sign(par, \{ pk_1,…,pk_n \} ,sk,m) : s_i \leftarrow H_0(m)^{a_i \cdot sk_i} , where\ a_i \leftarrow H_1(pk_i, \{ pk_1,…,pk_n \} )\)

每个用户 \(i\) 对消息 \(m\) 用私钥签名,得出签名 \(s_i\)

\(\sigma \leftarrow \prod_{j=1}^n s_j\)

再将签名发给一个节点,节点进行聚合计算,生成聚合签名 \(\sigma\)

6). 验证

\(Vf(par,apk,m, \sigma ) : \ e( \sigma , g_2^{-1}) \cdot e(H_0(m),apk) = 1_{ \Bbb{G}_t}\)

将聚合公钥和聚合签名输入,进行两次双线性配对,若上面的式子成立,则说明验证成功;否则失败。

5.2

1). 准备阶段

有一个哈希方程 \(Hash: \{ 0, 1 \} ^* \rightarrow \Bbb{Z}_q\)

\(Hash\) 函数是将元素哈希到q阶整数群。

2). 参数生成

\(Pg(k): par \leftarrow (q,\Bbb{G}, \Bbb{G}_t,e,g) \leftarrow G(k)\)

生成一个双线性配对群,其中q是阶数,$ $ 是q阶群,\(e : \Bbb{G}_t \leftarrow \Bbb{G} × \Bbb{G}\) 是一种映射关系,\(g\)\(\Bbb{G}\) 的生成子。

3). 密钥生成

\(Kg(par) : sk_1,sk_2 \xleftarrow{random} \Bbb{Z}_q ; pk_1 \leftarrow g^{sk_1}; \ pk_2 \leftarrow g^{sk_2}\)

在整数群众随机抽取得出子私钥 \(sk_1,sk_2\) ,子公钥 \(pk_1,pk_2\) 由私钥计算得出。

最终输出私钥: \(SK=(sk_1,sk_2)\) , 公钥: \(PK=(par,pk_1,pk_2)\)

4). 签名

输入 \(message\) ,生成消息 \(M \leftarrow Hash(message)\) ,在群 $ $ 随机选取随机数 \(A \in \Bbb{G}\) , 计算 \(B \leftarrow A^{sk_2}, \ C \leftarrow A^{sk_1}B^{sk_1 \cdot M}\) ,最终输出对消息 \(M\) 的签名:$ (A,B,C)$ 。

5). 验证

输入对消息 \(M\) 的签名 $ (A,B,C)$ ,使用公钥 \(PK\) 验证,若

\(e(A,pk_2)=e(B,g)\)\(e(C,g)=e(A,pk_1) \cdot e(B,pk_1)^M\) 则验证成功。

5.3 Anonymous credentials with type-3 revocation

这是Indy项目中Credential(凭证)的整体方案,包括凭证颁发凭证存储凭证选择性披露与验证凭证撤销

在整个Credential的方案中有三种角色:issuer(凭证发行人)、holder(凭证持有人)、verifier(验证者)。这种方案是基于Camenisch-Lysyanskaya Sign方案改进的,可实现选择性披露、“一次性”凭证的功能。

详情可看这个PDF文档 (建议由密码学功底的人看)

5.4 Shamir Secret Sharing Scheme

TODO

四、Hyperledger Aries

Hyperledger Aries 是今年5月Hyperledger宣布开源的项目,目前该项目还处于初始阶段,Aires 的产品包括:

  • 用于创建和签名区块链事务的区块链接口层(称为解析器)
  • 安全存储(安全存储技术)用于构建区块链客户机加密信息和其他信息的加密钱包
  • 一种加密的消息传递系统,用于使用多种传输协议在客户端之间进行账外交互
  • 使用 Ursa 中的 ZKP 原语实现支持 ZKP 的 W3C 可验证凭证
  • 一种去中心化密钥管理系统(DKMS)规范
  • 一种基于安全消息传递功能构建的高级协议和类API用例的机制

我个人认为这是Hyperledger Indy项目的一种扩展延伸,实现功能点几乎一样;

但是,Indy是专注于为身份认证而建立的特定区块链,Aries是不适用区块链的项目,项目组期望能在Aries成熟之后融入Indy项目。Hyperledger之后会将Indy项目中与区块链无关的功能迁移到Aries项目,Induy专注于区块链功能。

五、落地案例

主要落地应用是在加拿大British Columbia省的 Verfiable Organizations Network(VON),该项目已开源(链接),目的在于帮助商人建立可信的持续的电子身份验证,从而加速申请政府机构的许可证和执照。

该项目已经在2019年1月推出上线,减少了新供应商或客户进行尽职调查的时间。下一步目标是扩展应用程序,鼓励更多的司法管辖区域使用。

六、推广模式

目前Indy的推广模式相关文章/报道较少(可能也是我菜,找不到/不会找 _(:3」∠)_ )

  • 以投资者的角度分析了Sovrin项目 (链接)
  • 采访Sovrin主创报道文章,主要讲述了Sovrin是什么,要解决什么问题,怎么解决的,展望未来(链接)

七、Q&A

Q:Hyperledger Indy是否支持智能合约?

A:Indy专注于数字身份管理,Indy不是为了支持任何资产交换而开发的,而是为了解决与我们当前数字环境的问题。该项目旨在取代我们用来通过分散机制验证用户数字身份的主流机制,例如用户名 - 密码组合,这种机制更安全可靠。由于其独特的使用案例,Indy无法支持任何智能合约。

Q:Authentication是如何进行的?授权的粒度?

Indy对DID、Credential以及节点的授权都有不同,详情可见链接

Q:是否支持多个区块链平台?

A:目前Indy项目的底层是Plenum BFT,共识并没有模块化,底层耦合度较高,所以不支持

Q:用户和机构如何介入?

A:详情可见链接1链接2

Q:区块节点部署方式?

A:目前Indy节点支持本地部署(macOS,Ubuntu),容器部署(docker)以及云端部署,都有一键部署工具可用。

参考链接/文献

  1. Hyperledger Indy/Sovrin/DID Comprehensive Architecture Reference Model (INDY ARM)
  2. hyperledger/indy-agent
  3. hyperledger/indy-sdk
  4. decentralized-identity/universal-resolver
  5. hyperledger/indy-node
  6. hyperledger/indy-plenum
  7. Decentralized Identifiers (DIDs) v0.13
  8. hyperledger/indy-crypto
  9. hyperledger/indy-hipe
  10. Aublin, P.-L & Mokhtar, Sonia & Quéma, Vivien. (2013). RBFT: Redundant byzantine fault tolerance. Proceedings - International Conference on Distributed Computing Systems. 297-306. 10.1109/ICDCS.2013.53.
  11. Plenum Diagrams
  12. Libsodium document
  13. jsonpickle: Python library for serializing any arbitrary object graph into JSON.
  14. Merkling in Ethereum
  15. Current implemented rules in auth_map
  16. Dan Boneh, Manu Drijvers, and Gregory Neven. Compact multi-signatures for 62 smaller blockchains. Cryptology ePrint Archive, 2018.
  17. Camenisch J., Lysyanskaya A. (2003) A Signature Scheme with Efficient Protocols. In: Cimato S., Persiano G., Galdi C. (eds) Security in Communication Networks. SCN 2002. Lecture Notes in Computer Science, vol 2576. Springer, Berlin, Heidelberg
  18. Hyperledger Aries is infrastructure for blockchain-rooted, peer-to-peer interactions
  19. Exploring Hyperledger Indy through indy-dev Example
  20. Verifiable Organizations Network: Global digital trust for organizations
  21. Verifiable Organizations Network
  22. Reducing Government Red Tape: British Columbia Creates New Business Identity Model with Hyperledger Indy
  23. Does Hyperledger Indy support any kind of smart contracts?
  24. Add Node to Existing Pool
  25. Create a Network and Start Nodes
  26. Camenisch, J., Kohlweiss, M., Soriente, C.: An Accumulator Based on Bilinear Maps and Efficient Revocation for Anonymous Credentials. In Jarecki, S., Tsudik, G., eds.: Public Key Cryptography. Volume 5443 of Lecture Notes in Computer Science., Springer (2009) 481–500
  27. Anonymous credentials with type-3 revocation. Dmitry Khovratovich, Michael Lodder. 9 February 2018, version 0.4
  28. TokenGazer深度研究:Sovrin,技术落地可期,但推广难度较高
  29. Use case spotlight: The Government of British Columbia uses the Sovrin Network to take strides towards a fully digital economy
Buy me coffee☕️.