当前位置: 首页 > news >正文

通过同态加密实现可编程隐私和链上合规

1. 引言

2023年9月28日,a16z 的加密团队发布了 Nakamoto Challenge,列出了区块链中需要解决的最重要问题。尤其是其中的第四个问题格外引人注意:“合规的可编程隐私”,因为Zama团队已经在这方面积极思考了一段时间。本文提出了使用同态加密和Zama fhEVM 机密智能合约协议的第一个解决方案。

fhEVM 是一个常规的 EVM,它通过一些预编译功能使得使用Zama TFHE-rs 同态加密库可以对加密状态进行计算。从开发者的角度来看,这里没有涉及加密学:

  • 他们只需编写使用所提供的加密数据类型(如 euint32、ebool 等)的 Solidity 代码。

与其他隐私解决方案相比,fhEVM 的一个大优势是所有数据和计算都发生在链上。这意味着可以像常规的明文合约一样,拥有相同的组合性和数据可用性。【本文各种接口已被重构,理解思想即可。】

这一特性是构建可编程隐私的关键,因为所有的访问控制逻辑都可以在合约本身中定义。协议中没有任何需要硬编码的部分,用户也不需要进行链下操作来确保合规性。应用可以直接强化执行合规性,仅用几行 Solidity 代码!

本文将展示如何使用链上 DID 构建一个合规的 ERC20 代币。

2. 通过链上机密 DID 实现身份抽象

去中心化标识符(Decentralized Identifier,DID)是一种由实体(如政府、登记机关、公司或用户本人)颁发的独特数字身份。DID 可以与一个加密密钥相关联,用于证明用户拥有该 DID,如 EVM 钱包。但它也可以存储一整套属性,如用户的年龄、国籍、社会安全号码等。这些属性可以用来证明你满足某些条件(称为“证明”),如超过18岁或不是纳尼亚国公民。

大多数 DID 实现是在客户端进行的,并使用零知识证明生成证明。虽然在许多情况下这已经足够,但当涉及多个用户参与交易、需要对 DID 应用复杂规则或需要为所有人维护一套共同的规则时,这个方案会变得复杂起来。这实际上是边缘计算与云计算应用之间的取舍问题。

然而,拥有一个集中式的 DID 注册中心可以解决这些问题,因为可以简单地请求注册中心来检查每个人是否符合要求。它还可以简化监管的追踪,因为只需要在一个地方实现它。区块链将是一个完美的基础设施,因为它可以实现 DID 与需要合规性的应用之间的组合性,以及不同法规之间的组合性。

问题是:每个人都能看到每个人的身份!

幸运的是,有一个解决方案:

  • 同态加密,具体来说是 fhEVM!
    • 得益于对加密状态的组合性能力,可以直接将用户的 DID 以加密形式托管在链上,并让合规应用通过简单的合约调用来验证属性。
    • 通过智能合约管理身份的能力,称之为“身份抽象”,这类似于如何通过智能合约管理资金的账户抽象。

这个教程分为三部分:

  • 1)身份抽象:是通过一个注册合约来完成的,注册合约负责管理身份和证明。在这里,假设 DID 是官方政府身份证明。注册中心由一个中央机构(如 AFNIC)管理,后者可以创建注册商(如 KYC 公司,如 Onfido、Jumio 等),然后这些注册商可以创建用户 DID。用户通过他们的注册商来管理和更新他们的 DID。
  • 2)监管:在一个合约中定义,该合约编码了一套基于用户 DID 中信息的规则,用于管理个人之间的代币转移。它基本上在合约级别强制执行监管,而不是在用户级别。
  • 3)合规的机密转账:在一个合规的 ERC20 合约中实现,该合约使用监管合约来执行代币转移中的合规性,而不需要对 ERC20 API 本身进行任何更改。在这个示例中,使用了一个机密 ERC20 合约,其中余额和金额是隐藏的,但它同样适用于常规的明文 ERC20 代币。

Zama链上机密 DID 协议架构:
在这里插入图片描述

3. 身份注册合约

IdentityRegistry 合约是一个用户 DID 的注册中心,这些 DID 是由注册商颁发的,并包含一组加密的标识符,如用户的国籍、年龄、社会安全号码等。这些标识符作为加密的 32 位值(euint32)存储。

该合约还处理权限管理,如下所示:

  • 允许合约所有者(如 AFNIC)添加、移除或更新注册商。
  • 允许注册商添加、移除或更新他们创建的用户 DID。
  • 允许用户授权智能合约访问他们 DID 的特定属性。需要注意的是,用户有责任不向恶意合约提供访问权限,就像他们有责任不让恶意合约花费他们的代币一样。

第一步,实现创建和管理 DID 的逻辑:

// SPDX-License-Identifier: BSD-3-Clause-Clear
pragma solidity 0.8.19;import "fhevm/lib/TFHE.sol";contract IdentityRegistry is EIP712WithModifier, Ownable {// 从钱包到注册商ID的映射mapping(address => uint) public registrars;// 从钱包到身份的映射mapping(address => Identity) internal identities;struct Identity {uint registrarId;mapping(string => euint32) identifiers;}mapping(address => mapping(address => mapping(string => bool))) permissions; // 用户 => 合约 => 标识符[]event NewRegistrar(address wallet, uint registrarId);event NewDid(address wallet);event RemoveDid(address wallet);constructor() Ownable() EIP712WithModifier("Authorization token", "1") {_transferOwnership(msg.sender);}// 添加注册商function addRegistrar(address wallet, uint registrarId) public onlyOwner {require(registrarId > 0, "registrarId needs to be > 0");registrars[wallet] = registrarId;emit NewRegistrar(wallet, registrarId);}function removeRegistrar(address wallet) public onlyOwner {delete registrars[wallet];}function getRegistrar(address wallet) public view returns (uint) {return identities[wallet].registrarId;}// 添加用户 DIDfunction addDid(address wallet) public onlyRegistrar {require(identities[wallet].registrarId == 0, "This wallet is already registered");Identity storage newIdentity = identities[wallet];newIdentity.registrarId = registrars[msg.sender];emit NewDid(wallet);}function removeDid(address wallet) public onlyExistingWallet(wallet) onlyRegistrarOf(wallet) {require(identities[wallet].registrarId > 0, "This wallet isn't registered");delete identities[wallet];emit RemoveDid(wallet);}modifier onlyExistingWallet(address wallet) {require(identities[wallet].registrarId > 0, "This wallet isn't registered");_;}modifier onlyRegistrar() {require(registrars[msg.sender] > 0, "You're not a registrar");_;}modifier onlyRegistrarOf(address wallet) {uint registrarId = registrars[msg.sender];require(identities[wallet].registrarId == registrarId, "You're not managing this identity");_;}
}

其中:

  • onlyRegistraronlyRegistrarOf 注册商(Registrar):只有注册商可以添加或删除用户的 DID。每个钱包都与一个注册商 ID 相关联,注册商 ID 必须大于 0。
  • 身份管理:每个用户都有一个关联的 Identity,其中包含该用户的注册商 ID 和一组加密的标识符(如年龄、国籍等)。
  • 权限管理:用户可以授权特定的智能合约访问其 DID 的特定属性。合约会验证这些权限,确保只有经过授权的合约可以访问用户的身份信息。
  • 合约功能:
    • addRegistrar:允许合约所有者添加新的注册商。
    • removeRegistrar:允许合约所有者移除注册商。
    • addDid:允许注册商为钱包添加 DID。
    • removeDid:允许注册商移除钱包的 DID。
    • getRegistrar:查询用户 DID 所属的注册商 ID。

通过这种方式,可以在链上安全地管理和更新用户的身份信息,同时保证隐私性和合规性。

现在,下一步是实现identifiers标识符和访问控制的逻辑。

标识符只是一个字符串(如“出生日期”)和一个加密的 32 位值。它只能由注册商创建或更新,用户不能创建自己的标识符,因为希望这些标识符由注册商进行认证。

然而,由于标识符是加密的,用户需要授权某个合约访问特定的值,将通过一个简单的访问控制机制来处理这一点,这类似于你如何允许合约花费你的 ERC20 代币。

contract IdentityRegistry is EIP712WithModifier, Ownable {...mapping(address => mapping(address => mapping(string => bool))) permissions; // 用户 => 合约 => 标识符[]// 设置用户的标识符function setIdentifier(address wallet, string calldata identifier, bytes calldata encryptedValue) public {euint32 value = TFHE.asEuint32(encryptedValue);setIdentifier(wallet, identifier, value);}function setIdentifier(address wallet,string calldata identifier,euint32 value) internal onlyExistingWallet(wallet) onlyRegistrarOf(wallet) {identities[wallet].identifiers[identifier] = value;}// 用户处理权限function grantAccess(address allowed, string[] calldata identifiers) public {for (uint i = 0; i < identifiers.length; i++) {permissions[msg.sender][allowed][identifiers[i]] = true;}}function revokeAccess(address allowed, string[] calldata identifiers) public {for (uint i = 0; i < identifiers.length; i++) {delete permissions[msg.sender][allowed][identifiers[i]];}}...
}

现在可以通过添加必要的 getter 来完成身份注册合约,同时加入一些条件和错误处理。

contract IdentityRegistry is EIP712WithModifier, Ownable {...// 获取加密的标识符function reencryptIdentifier(address wallet,string calldata identifier,bytes32 publicKey,bytes calldata signature) public view onlySignedPublicKey(publicKey, signature) returns (bytes memory) {euint32 ident = _getIdentifier(wallet, identifier);require(TFHE.isInitialized(ident), "This identifier is unknown");return TFHE.reencrypt(ident, publicKey, 0);}function getIdentifier(address wallet, string calldata identifier) public view returns (euint32) {return _getIdentifier(wallet, identifier);}function _getIdentifier(address wallet,string calldata identifier) internal view onlyExistingWallet(wallet) onlyAllowed(wallet, identifier) returns (euint32) {return identities[wallet].identifiers[identifier];}modifier onlyAllowed(address wallet, string memory identifier) {require(owner() == msg.sender || permissions[wallet][msg.sender][identifier],"User didn't give you permission to access this identifier.");_;}
}

其中:

  • setIdentifier:允许注册商为钱包设置新的标识符。标识符是通过加密的 32 位值进行存储。
  • grantAccessrevokeAccess:允许用户授权或撤销合约访问其标识符的权限,权限是基于合约和标识符的。
  • reencryptIdentifier:允许用户通过签名证明他们的身份,并获取加密的标识符。这是为了确保数据的安全传输和使用。
  • getIdentifier_getIdentifier:获取标识符的加密值,只有授权的合约和用户才能访问。
  • onlyAllowed:访问控制修饰符,确保只有授权的用户和合约可以访问特定的标识符。

通过这样的设计,可以确保用户的身份信息在链上安全存储,同时保证合规性和隐私保护。

4. 规则合约

下一步是创建规则合约。

在为两个个体之间的转账实现一组规则时,需要认识到这些规则可能会随着时间的推移而变化。拥有一个单一的智能合约来定义给定上下文(如货币转账)的所有规则,意味着 ERC20 合约不必自己跟踪规则。政府可以简单地更新这个合约,并且它会自动传播到所有实现了该合约的代币。

从本质上讲,规则合约只是一组与加密的身份属性匹配的条件。为了避免滥用,用户不会直接授予规则合约访问权限,而是将权限授予 ERC20 代币合约,然后它执行一个委托调用到规则合约。这样做确保了只有用户信任的 ERC20 合约才能访问他们的信息。请记住,在进行转账之前,发送方和接收方都必须授予 ERC20 合约访问权限。

在本例中将实现一些基本规则:

  • 在同一国家内的转账没有限制,但转账到其他国家的金额限制为 10,000 个代币。
  • 被列入黑名单的用户不能进行转账或接收代币。
  • 用户不能将代币转账到被列入黑名单的国家。

与其让交易失败(这可能会暴露敏感信息),将简单地将转账金额设置为 0,如果没有满足其中一个条件。这使用了一个同态三元操作符,称为 cmuxvalue = TFHE.cmux(encryptedCondition, valueIfTrue, valueIfFalse);

// SPDX-License-Identifier: BSD-3-Clause-Clearpragma solidity 0.8.19;import "fhevm/lib/TFHE.sol";
import "./IdentityRegistry.sol";interface ICompliantERC20 {function getIdentifier(address wallet, string calldata identifier) external view returns (euint32);
}contract ERC20Rules {string[] public identifiers;mapping(address => uint32) public whitelistedWallets;mapping(string => uint8) public countries;uint16[] public country2CountryRestrictions;constructor() {identifiers = ["country", "blacklist"];whitelistedWallets[address(0x133725C6461120439E85887C7639316CB27a2D9d)] = 1;whitelistedWallets[address(0x4CaCeF78615AFecEf7eF182CfbD243195Fc90a29)] = 2;countries["fr"] = 1;countries["us"] = 2;country2CountryRestrictions = [createRestriction(countries["us"], countries["fr"])];}function createRestriction(uint16 from, uint16 to) internal pure returns (uint16) {return (from << 8) + to;}function getIdentifiers() public view returns (string[] memory) {return identifiers;}function getC2CRestrictions() public view returns (uint16[] memory) {return country2CountryRestrictions;}function transfer(address from, address to, euint32 amount) public view returns (euint32) {ICompliantERC20 erc20 = ICompliantERC20(msg.sender);// 条件 1:不同国家之间的转账限额为 10,000 个代币ebool transferLimitOK = checkLimitTransfer(erc20, from, to, amount);ebool condition = transferLimitOK;// 条件 2:检查是否有黑名单用户ebool blacklistOK = checkBlacklist(erc20, from, to);condition = TFHE.and(condition, blacklistOK);// 条件 3:检查国家间转账规则ebool c2cOK = checkCountryToCountry(erc20, from, to);condition = TFHE.and(condition, c2cOK);return TFHE.cmux(condition, amount, TFHE.asEuint32(0));}function checkLimitTransfer(ICompliantERC20 erc20,address from,address to,euint32 amount) internal view returns (ebool) {euint8 fromCountry = TFHE.asEuint8(erc20.getIdentifier(from, "country"));euint8 toCountry = TFHE.asEuint8(erc20.getIdentifier(to, "country"));require(TFHE.isInitialized(fromCountry) && TFHE.isInitialized(toCountry), "You don't have access");ebool sameCountry = TFHE.eq(fromCountry, toCountry);ebool amountBelow10k = TFHE.le(amount, 10000);return TFHE.or(sameCountry, amountBelow10k);}function checkBlacklist(ICompliantERC20 erc20, address from, address to) internal view returns (ebool) {ebool fromBlacklisted = TFHE.asEbool(erc20.getIdentifier(from, "blacklist"));ebool toBlacklisted = TFHE.asEbool(erc20.getIdentifier(to, "blacklist"));return TFHE.not(TFHE.and(toBlacklisted, fromBlacklisted));}function checkCountryToCountry(ICompliantERC20 erc20, address from, address to) internal view returns (ebool) {// 禁止从国家 2 转账到国家 1uint16[] memory c2cRestrictions = getC2CRestrictions();euint32 fromCountry = erc20.getIdentifier(from, "country");euint32 toCountry = erc20.getIdentifier(to, "country");require(TFHE.isInitialized(fromCountry) && TFHE.isInitialized(toCountry), "You don't have access");euint16 countryToCountry = TFHE.shl(TFHE.asEuint16(fromCountry), 8) + TFHE.asEuint16(toCountry);ebool condition = TFHE.asEbool(true);// 检查所有国家间的转账限制for (uint i = 0; i < c2cRestrictions.length; i++) {condition = TFHE.and(condition, TFHE.ne(countryToCountry, c2cRestrictions[i]));}return condition;}
}

其中:

  • createRestriction:创建一个国家到国家的转账限制,两个国家通过一个 16 位的编码表示。
  • getIdentifiers:返回所有的标识符数组。
  • getC2CRestrictions:返回所有的国家间转账限制。
  • transfer:检查转账是否满足所有条件(如限额、黑名单、国家间转账规则),如果任何条件不满足,则将转账金额设置为 0。
  • checkLimitTransfer:检查转账是否超出了不同国家之间的限额。
  • checkBlacklist:检查发送方和接收方是否在黑名单中。
  • checkCountryToCountry:检查两个国家之间是否允许转账。

这种设计可以确保在转账过程中遵循规则和合规性要求,同时保障用户的隐私和安全。

5. 合规的机密 ERC20 合约

现在有了身份注册合约和规则合约,接下来创建合规、隐私保护的代币合约。这个合约将被称为 CompliantERC20,并具有以下关键特性:

  • 用户余额和转账金额是加密的。
  • 转账时通过调用规则合约来强制执行合规性。
  • 某些余额的可见性可以授予白名单地址(如监管机构)。
// SPDX-License-Identifier: BSD-3-Clause-Clearpragma solidity 0.8.19;import "fhevm/abstracts/EIP712WithModifier.sol";
import "./ERC20Rules.sol";
import "./IdentityRegistry.sol";abstract contract AbstractCompliantERC20 is EIP712WithModifier {IdentityRegistry identityContract;ERC20Rules rulesContract;mapping(address => euint32) internal balances;constructor(address _identityAddr, address _rulesAddr) EIP712WithModifier("Authorization token", "1") {identityContract = IdentityRegistry(_identityAddr);rulesContract = ERC20Rules(_rulesAddr);}function identifiers() public view returns (string[] memory) {return rulesContract.getIdentifiers();}function getIdentifier(address wallet, string calldata identifier) external view returns (euint32) {require(msg.sender == address(rulesContract), "Access restricted to the current ERC20Rules");return identityContract.getIdentifier(wallet, identifier);}function balanceOf(address wallet,bytes32 publicKey,bytes calldata signature) public view onlySignedPublicKey(publicKey, signature) returns (bytes memory) {// 用户可以查看自己的余额if (wallet == msg.sender) {return TFHE.reencrypt(balances[msg.sender], publicKey, 0);}// 国家可以查看所有公民的余额uint32 userCountry = rulesContract.whitelistedWallets(msg.sender);require(userCountry > 0, "You're not registered as a whitelisted wallet");euint32 walletCountry = identityContract.getIdentifier(wallet, "country");ebool sameCountry = TFHE.eq(walletCountry, userCountry);euint32 balance = TFHE.isInitialized(balances[wallet]) ? balances[wallet] : TFHE.asEuint32(0);balance = TFHE.cmux(sameCountry, balance, TFHE.asEuint32(0));return TFHE.reencrypt(balance, publicKey, 0);}// 转账加密金额function _transfer(address from, address to, euint32 _amount) internal {// 条件 1:资金充足ebool enoughFund = TFHE.le(_amount, balances[from]);euint32 amount = TFHE.cmux(enoughFund, _amount, TFHE.asEuint32(0));amount = rulesContract.transfer(from, to, amount);balances[to] = balances[to] + amount;balances[from] = balances[from] - amount;}
}

其中:

  • balanceOf:此方法根据用户的公钥和签名返回加密后的余额。用户只能查看自己的余额,如果是同一国家的用户,国家可以查看所有公民的余额。
  • _transfer:在转账过程中,通过规则合约来强制执行合规性,确保资金充足后,转账金额经过合规性检查,然后将金额从发送方转账到接收方。

在这个合约中,规则合约是通过简单的调用来触发的。这意味着用户必须在发起任何转账之前提供对 ERC20 合约的访问权限;否则,转账将被回滚。

最终,创建的 ERC20 合约:

// SPDX-License-Identifier: BSD-3-Clause-Clear
pragma solidity 0.8.19;import "fhevm/lib/TFHE.sol";
import "./AbstractCompliantERC20.sol";contract CompliantERC20 is AbstractCompliantERC20 {...// 从消息发送者地址转账加密金额到 `to` 地址function transfer(address to, bytes calldata encryptedAmount) public {transfer(to, TFHE.asEuint32(encryptedAmount));}// 从消息发送者地址转账金额到 `to` 地址function transfer(address to, euint32 amount) public {_transfer(msg.sender, to, amount);}
}

与用户授权 DeFi 协议花费他们的代币类似,用户需要授权合约访问规则合约所需的标识符。这是通过调用 Identity.grantAccess(contractAddress, identifiers) 来实现的,标识符可以通过调用 ERC20.identifiers() view方法来获取。这个列表直接来自 ERC20Rules 合约,用于允许属性的更新。

6. 合规性与隐私可以共存!

如果拥有正确的工具,构建合规性并不是一件困难的事情。虽然Zama最初构建了 fhEVM 以在区块链中实现隐私保护,但很快意识到,这项技术可以用于身份管理,从而实现可编程的合规性。

参考资料

[1] Zama团队2023年11月23日博客 Programmable Privacy and Onchain Compliance using Homomorphic Encryption

http://www.lryc.cn/news/586732.html

相关文章:

  • 终端输入命令,背后发生了什么--shell,tty,terminal解析
  • 数据结构 单链表(1)
  • 以太坊应用开发基础:从理论到实战的完整指南
  • 完整 Spring Boot + Vue 登录系统
  • 20250711_Sudo 靶机复盘
  • Http与Https区别和联系
  • linux:进程详解(2)
  • Excel的学习
  • SQL的初步学习(二)(以MySQL为例)
  • 基于 SpringBoot 的 REST API 与 RPC 调用的统一封装
  • JavaScript 获取 URL 参数值的全面指南
  • DOS下用TC2显示Bmp文件
  • Cesium初探-CallbackProperty
  • 单页面和多页面的区别和优缺点
  • 退出登录后头像还在?这个缓存问题坑过多少前端!
  • 开发语言的优劣势对比及主要应用领域分析
  • DNS协议解析过程
  • 前端进阶之路-从传统前端到VUE-JS(第五期-路由应用)
  • 开发语言中关于面向对象和面向过程的笔记
  • 【Qt开发】Qt的背景介绍(一)
  • docker容器高级管理-dockerfile创建镜像
  • RabbitMQ面试精讲 Day 2:RabbitMQ工作模型与消息流转
  • Netty主要组件和服务器启动源码分析
  • EWSGAN:自动搜索高性能的GAN生成器架构
  • Kotlin 类和对象
  • JS红宝书pdf完整版
  • HarmonyOS组件/模板集成创新活动-开发者工具箱
  • 2025.7.13总结
  • Nature子刊 |HERGAST:揭示超大规模空间转录组数据中的精细空间结构并放大基因表达信号
  • 直流/直流电源模块:无干扰布线,避免电磁干扰的技术方案