Solidity 隐私代币揭秘:自动回调与操作员机制详解

引言

在以太坊智能合约的开发中,传统的代币转账方式通常依赖于用户直接调用 transfer 或 transferFrom 方法。然而,这种方式在某些场景下可能不够灵活,尤其是当需要第三方代表用户执行代币操作时。为了解决这一问题,引入了“操作员(Operator)”机制,使得用户可以授权第三方在特定条件下代表自己进行代币操作。

在 OpenZeppelin 的 Confidential Fungible Token(CFT)实现中,除了传统的转账方式,还引入了带回调(callback)的转账函数 confidentialTransferWithCallback。这种方式允许在代币转账完成后,自动触发接收方合约的特定函数,从而实现更复杂的业务逻辑。

本文将详细介绍操作员机制的实现方式,以及带回调的转账函数如何在发送方和接收方合约中协同工作,实现代币的安全转移和自动处理。

一、Operator

在标准的 ERC-20 代币合约中,用户只能直接调用 transfertransferFrom 方法来转移代币。然而,在某些场景下,我们希望允许第三方代表用户进行代币转移操作,这时就引入了操作员机制。操作员是被授权代表用户执行特定操作的地址,通常具有一定的权限和有效期限。

const alice: Wallet;
const expirationTimestamp = Math.round(Date.now() / 1000) + 60 * 60 * 24; // 当前时间 + 24 小时

await tokenContract.connect(alice).setOperator(bob, expirationTimestamp);

代码解释

创建一个钱包实例 alice,用于签名交易。
计算当前时间加上 24 小时后的时间戳,作为操作权限的过期时间。
通过 alice 钱包连接到代币合约,并调用 setOperator 方法,将 bob 设置为操作员,权限在指定的时间戳后过期。

二、Callback

回调就是转账完成后,“自动触发”对接收方合约的特定函数进行调用。
如果你选择使用带回调的转账函数,就不需要再手动调用中间授权(如“操作员审批”)步骤,系统会自动处理。

被回调函数调用的智能合约必须实现 IConfidentialFungibleTokenReceiver 接口。

发送方合约(Token 合约)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {IConfidentialFungibleTokenReceiver} from "@openzeppelin/confidential-contracts/interfaces/IConfidentialFungibleTokenReceiver.sol";
import {ConfidentialFungibleToken} from "@openzeppelin/confidential-contracts/token/ConfidentialFungibleToken.sol";

contract ConfidentialToken is ConfidentialFungibleToken {
    constructor() ConfidentialFungibleToken("ConfidentialToken", "CFT") {}

    function confidentialTransferWithCallback(
        address recipient,
        externalEuint64 encryptedAmount,
        bytes calldata inputProof,
        bytes calldata callbackData
    ) external {
        confidentialTransferFrom(msg.sender, address(this), encryptedAmount, inputProof);

        bool success = IConfidentialFungibleTokenReceiver(recipient)
            .onConfidentialTransferReceived(
                msg.sender,
                encryptedAmount,
                callbackData
            );

        require(success, "Callback failed");
    }
}

接收方合约(Receiver)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {IConfidentialFungibleTokenReceiver} from "@openzeppelin/confidential-contracts/interfaces/IConfidentialFungibleTokenReceiver.sol";

contract Receiver is IConfidentialFungibleTokenReceiver {
    mapping(address => uint256) public balances;

    function onConfidentialTransferReceived(
        address from,
        bytes calldata ciphertext,
        bytes calldata data
    ) external override returns (ebool) {
        uint256 decryptedAmount = 100; // 假设解密后的金额为 100
        balances[from] += decryptedAmount;
        return true;
    }
}

代码解释

发送方调用 confidentialTransferWithCallback:将加密金额转账给接收方合约。

Token 合约自动调用回调函数:在转账完成后,自动调用接收方合约的 onConfidentialTransferReceived 方法。

接收方合约处理回调接收方合约在 onConfidentialTransferReceived 方法中进行加密金额的解密和验证。如果验证通过,更新接收方的余额并返回 true;否则,返回 false 或抛出异常,导致转账失败。

三、Swaps

(一)Swap ERC20 to ConfidentialFungibleToken

下面这段 Solidity 代码展示了一个将普通 ERC20 代币 **“包裹”(wrap)**成保密形式加密代币的过程。它主要做两件事:扣除用户的明文代币,然后 mint(铸造)等量的加密代币。

ERC20 代币仅需直接转入,若转入失败则交易自动回滚。随后,我们通过内部的 _mint 函数转出正确数量的 ConfidentialFungibleToken,该函数保证转出操作必成功。

function wrap(address to, uint256 amount) public virtual {
    // take ownership of the tokens
    SafeERC20.safeTransferFrom(underlying(), msg.sender, address(this), amount - (amount % rate()));

    // mint confidential token
    _mint(to, (amount / rate()).toUint64().asEuint64());
}

代码解释

1、函数签名及参数

wrap(address to, uint256 amount):

to 是接收新铸造的加密代币的地址;

amount 是用户想包裹的明文 ERC20 代币数量。

2、扣除用户明文代币

underlying() 返回被包裹的原始 ERC20 代币合约地址;

safeTransferFrom 从调用者 msg.sender 扣除 (amount - (amount % rate())) 数量的明文代币,转入当前合约;

amount % rate() 残余部分被抹去,确保只扣整倍的代币;

rate() 是一个兑换比例,比如 100:1 ——意味着你需要 100 个明文资产对应 1 个加密代币。

3、铸造对应加密代币

计算铸造数量:amount / rate(),得到你能获取多少单位的加密代币;

toUint64():确保铸造数量不超过 uint64 的最大值;

.asEuint64():将普通整数转换为加密整数类型 euint64;

_mint(to, …) 将等量的加密代币(保密状态)铸造/记账给 to 地址。

(二)Swap ConfidentialFungibleToken to ConfidentialFungibleToken

Solidity 代码实现了一个基于 Zama FHE 协议的加密代币交换功能,允许用户在链上进行隐私保护的代币交换。在 Zama 的 FHE 协议中,所有数据都是加密的,只有授权的合约才能解密和操作这些数据。

function swapConfidentialForConfidential(
    IConfidentialFungibleToken fromToken,
    IConfidentialFungibleToken toToken,
    externalEuint64 amountInput,
    bytes calldata inputProof
) public virtual {
    require(fromToken.isOperator(msg.sender, address(this)));

    euint64 amount = FHE.fromExternal(amountInput, inputProof);

    FHE.allowTransient(amount, address(fromToken));
    euint64 amountTransferred = fromToken.confidentialTransferFrom(msg.sender, address(this), amount);

    FHE.allowTransient(amountTransferred, address(toToken));
    toToken.confidentialTransfer(msg.sender, amountTransferred);
}

代码解释

require(fromToken.isOperator(msg.sender, address(this)));
作用:验证调用者(msg.sender)是否被授权为 fromToken 的操作员。

背景:在 Zama 的 FHE 协议中,只有被授权的操作员才能执行加密代币的转账操作。

euint64 amount = FHE.fromExternal(amountInput, inputProof);
作用:将外部传入的加密数据(amountInput)和证明(inputProof)转换为内部可操作的加密整数类型 euint64。

背景:euint64 是 Zama FHE 协议中用于表示加密整数的类型。

FHE.allowTransient(amount, address(fromToken));
作用:允许对 fromToken 合约地址的临时访问权限,以便执行加密代币的转账操作。

背景:在 FHE 协议中,操作加密数据需要临时授权。

euint64 amountTransferred = fromToken.confidentialTransferFrom(msg.sender, address(this), amount);
作用:从调用者(msg.sender)向当前合约(address(this))转移指定数量的加密代币。

背景:confidentialTransferFrom 是 Zama FHE 协议中用于执行加密代币转账的函数。

FHE.allowTransient(amountTransferred, address(toToken));
作用:允许对 toToken 合约地址的临时访问权限,以便将转账的加密代币发送给目标地址。

背景:在 FHE 协议中,操作加密数据需要临时授权。

toToken.confidentialTransfer(msg.sender, amountTransferred);
作用:将转账的加密代币发送到目标地址(msg.sender)。

背景:confidentialTransfer 是 Zama FHE 协议中用于执行加密代币转账的函数。

(三)Swap ConfidentialFungibleToken to ERC20

从保密代币切换到非保密代币是最复杂的操作,因为需要访问解密后的数据才能准确完成请求。在我们的示例中,解密操作将在链下进行,并通过 Zama 的网关将结果回传。以下是一个合约示例,演示如何将保密代币与 ERC20 代币进行 1:1 兑换。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

// 导入 Zama FHE 库,用于处理加密整数类型
import {FHE, externalEuint64, euint64} from "@fhevm/solidity/lib/FHE.sol";
// 导入标准 ERC20 接口
import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
// 导入 SafeERC20,用于安全转账
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
// 导入保密代币接口
import {IConfidentialFungibleToken} from "@openzeppelin/confidential-contracts/interfaces/IConfidentialFungibleToken.sol";

contract SwapConfidentialToERC20 {
    using FHE for *;

    // 自定义错误:无效的解密回调请求 ID
    error SwapConfidentialToERC20InvalidGatewayRequest(uint256 requestId);

    // 映射:存储每个请求 ID 对应的接收者地址
    mapping(uint256 => address) private _receivers;

    // 保密代币合约(输入端)
    IConfidentialFungibleToken private _fromToken;
    // 普通 ERC-20 代币合约(输出端)
    IERC20 private _toToken;

    // 构造函数:初始化输入和输出代币合约地址
    constructor(IConfidentialFungibleToken fromToken, IERC20 toToken) {
        _fromToken = fromToken;
        _toToken = toToken;
    }

    /**
     * @notice 用户调用此函数发起 swap,将加密代币提交;
     * @param encryptedInput 加密金额,externalEuint64 类型
     * @param inputProof 加密金额的零知识证明
     */
    function swapConfidentialToERC20(externalEuint64 encryptedInput, bytes memory inputProof) public {
        // 1. 使用 proof 将加密输入转为链上可用的加密整数
        euint64 amount = encryptedInput.fromExternal(inputProof);

        // 2. 授权保密代币合约读取 this 合约中的这笔加密金额
        amount.allowTransient(address(_fromToken));

        // 3. 从用户 transfer 保密代币到本合约
        euint64 amountTransferred = _fromToken.confidentialTransferFrom(msg.sender, address(this), amount);

        // 4. 将 ciphertext 添加到解密请求列表
        bytes32 ;
        cts[0] = euint64.unwrap(amountTransferred);

        // 5. 发出解密请求,并指定回调函数 finalizeSwap
        uint256 requestID = FHE.requestDecryption(cts, this.finalizeSwap.selector);

        // 6. 保存 requestID ↔ 用户地址 关联,等待回调发币
        _receivers[requestID] = msg.sender;
    }

    /**
     * @notice 解密服务调用此函数,将加密金额转成明文后,合约会发放同等 ERC-20 代币
     * @param requestID 与 swap 请求对应的唯一 ID
     * @param amount 解密后拿到的金额(uint64 明文)
     * @param signatures 多重签名验证解密合法性
     */
    function finalizeSwap(uint256 requestID, uint64 amount, bytes[] memory signatures) public virtual {
        // 1. 验证解密签名是否合法
        FHE.checkSignatures(requestID, signatures);

        // 2. 获取之前保存的接收方
        address to = _receivers[requestID];
        if (to == address(0)) revert SwapConfidentialToERC20InvalidGatewayRequest(requestID);

        // 3. 清除记录避免重复提现
        delete _receivers[requestID];

        // 4. 如果解密金额大于 0,将 ERC-20 转给接收者
        if (amount != 0) {
            SafeERC20.safeTransfer(_toToken, to, amount);
        }
    }
}

代码解释

注释写在代码上啦。这段 Solidity 合约 SwapConfidentialToERC20 用于将保密代币(encrypted token)兑换成普通 ERC‑20 代币,核心思路是用户先将加密金额转移到合约,触发链下解密过程,最后由合约释放等值的 ERC‑20。它做了三件事:接收保密代币、提交解密请求、解密成功后,释放相应 ERC‑20 代币

总结

通过引入操作员机制和带回调的转账函数,智能合约的代币操作变得更加灵活和安全。

操作员机制:用户可以通过 setOperator 方法授权第三方在指定时间内代表自己执行代币操作。这种方式避免了频繁的授权操作,提高了用户体验。

带回调的转账函数:发送方合约在执行代币转账后,自动调用接收方合约的 onConfidentialTransferReceived 方法,实现自动处理。这种方式简化了授权和转账的流程,提高了系统的自动化程度。

通过上述机制的结合,智能合约不仅实现了代币的安全转移,还能够在转账完成后自动触发特定的业务逻辑,为去中心化应用(DApp)提供了更强大的功能支持。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yoona1020

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值