欢迎光临
我们一直在努力

web3的跨链(HTLC原子交换 )python实现

哈希时间锁合约(HTLC)

哈希时间锁合约(Hashed Timelock Contract,HTLC)是一种智能合约,它允许将价值(如代币)锁定一段固定时间(或一定数量的区块)。在价值被锁定的期间,只有提供正确的密钥(哈希原像)时,才能将其转移给指定的接收方。这确保了价值的转移仅在特定条件下发生,即只有代币的所有者选择揭示密钥时才会发生。本文将展示在 Solidity 中实现 HTLC 的一种可能方式。

跨链原子交换

HTLC 的一个典型应用场景是(原子性地)在一条区块链上交付一种资产,以换取在另一条区块链上支付另一种资产。假设Alice希望在链 A 上向Bob发送某种代币(代币 1),而Bob希望使用链 B 上的某种代币(代币 2)向Alice支付。双方已在线下就交易条款达成一致。“顺利路径”(即双方在时间窗口结束前均未退出交易)的流程如下:

  • Alice生成一个随机的(密钥)原像,对其进行哈希处理,并使用该哈希值在链 A 上锁定她的代币。只有Bob在锁定时间结束前使用正确的哈希原像才能认领该代币。
  • 哈希值现在通过链 A 上的 HTLC 对Bob可见,因此他使用该哈希值在链 B 上的 HTLC 中锁定他的代币。
  • Alice随后可以使用她的原像认领链 B 上锁定的代币。
  • 由于Alice刚刚在链上揭示了原像,Bob使用它来认领链 A 上的代币。
  • 在这里插入图片描述

    如果在时间窗口结束后,HTLC中的代币仍未被认领,则发送方有权获得退款,而接收方无法再认领该代币。

    Solidity 设计

    为了编写 Solidity 实现,我们需要选择可以在 HTLC 中锁定的价值类型。为了保持通用性且不过度复杂化合约,本示例将支持任何符合 ERC-20 标准的代币。最简单的选项是仅使用以太币本身,但这会大大限制其潜在应用。由于接口简单,支持任何 ERC-20 代币都是直接的,并且可以推广到各种可能的代币,包括(包装的)以太币。其他选项可能包括 ERC-721 或 ERC-1155 代币。可以轻松调整此示例以支持这些其他代币标准。

    在每个 ERC-20 合约中包含一个 HTLC 实现将是浪费且不可行的。相反,我们应该创建一个单独的、与 ERC-20 合约分离的 HTLC,该 HTLC 可以与任意数量的 ERC-20 代币交互。这利用了 Solidity 合约的可组合性。

    以下是 HTLC 及其与 ERC-20 代币交互的状态机图。转换所需的先决条件在括号中指示,t 表示区块链提供的当前时间。初始状态由一个没有来源的箭头指向。每个状态转换都是通过调用 HTLC 上的(外部)方法执行的。该图仅涉及与Alice-Bob交易相关的状态。

    在这里插入图片描述

    最初,Alice是(未锁定的)代币的所有者,代币存储在 ERC-20 合约中。锁定是通过将代币的所有权从Alice转移到 HTLC 本身来实现的,从而防止除 HTLC 指定条件外的任何转移。只要Bob提供一个原像 p,使得 h§ 等于Alice提供的哈希值,他就可以认领该代币,其中 h 是哈希函数(本例中为 keccak256)。

    以下是 HTLC 的存储示例:

    pragma solidity >=0.8.0 <0.9.0;
    contract HTLC {
    struct Lock {
    uint unlockTime;
    uint amount;
    address tokenAddress;
    address senderAddress;
    address receiverAddress;
    }

    mapping(bytes32 => Lock) public locks;


    }

    Lock 结构体存储了锁定特定代币所需的所有信息,除了哈希值。我们可以在 locks 映射中存储任意一组锁,每个锁都通过哈希值进行索引。为了简单起见,我选择了通过哈希值进行索引,但为了使其更通用,可以使用不同的索引,以便使用相同的哈希值锁定多个代币。

    可能的状态转换通过以下方法公开:

    contract HTLC {

    function claim(bytes calldata preImage) external { … }

    function lock(
    bytes32 hashValue,
    uint unlockTime,
    uint amount,
    address tokenAddress,
    address receiverAddress
    ) external { … }

    function retake(bytes32 hashValue) external { … }
    }

    新的锁条目通过 lock 方法插入到 locks 中。如果哈希值已被使用,则此方法将回滚交易。请注意,lock 方法将代币从 msg.sender 转移到 HTLC,因此 msg.sender 必须预先批准 HTLC 使用 ERC-20 的 approve 方法代表她执行此转移。

    claim 方法允许 msg.sender 通过哈希处理提供的 preImage 并检查其是否存在于 locks 中来接收锁定的代币。如果存在且解锁时间未到,则检查 msg.sender 是否等于 receiverAddress,之后可以转移代币。请注意,在转移代币之前必须删除 locks 中的条目,以避免重入攻击。

    retake 方法的操作与 claim 类似,但不需要原像。

    Web3.py 跨链原子交换(HTLC)模拟

    完整流程如下:

    🌕 步骤 1:Alice 生成 S 和 H

    Alice 创建:

    S(秘密)
    H = SHA256(S)


    🌕 步骤 2:Alice 在 A 链上创建 HTLC_A → Bob

    这个 HTLC 内容是:

    • 如果 Bob 提供秘密 S + Bob 签名 → Bob 拿 Token_A
    • 过期后 Alice 退款

    Alice 把:

    • HTLC 的 txid
    • H(哈希)

    发送给 Bob。

    👉 注意:Alice 并没有泄露 S,只泄露了 H。 H 不能推导出 S。


    🌕 步骤 3:Bob 检查 Alice 的 HTLC,确保资金已经锁定

    🌕 步骤 4:Bob 在 B 链上创建自己的 HTLC_B → Alice

    Bob 会锁定 Token_B,条件同样是:

    • 如果 Alice 提供 S + Alice 签名 → Alice 拿 Token_B
    • 过期后 Bob 退款

    现在两边变成:

    Alice 已锁 Token_A
    Bob 已锁 Token_B

    此时交换才正式成立。


    🌕 步骤 5:Alice 在 B 链上用 S 提取 Token_B(暴露秘密)

    当 Alice 在 B 链上执行提款交易时:

    • S 会被自动记录在区块链数据中
    • Bob 会看到这个 S

    🌕 步骤 6:Bob 在 A 链上用 S + Bob 私钥签名取走 Token_A

    交换完成。


    🔐 核心安全论证总结

    • ✔ Bob 想取 Token_A → 需要 S
    • ✔ 想得到 S → 必须等 Alice 在 B 链上用 S 取 Token_B
    • ✔ Alice 想取 Token_B → 需要 Bob 的 HTLC 已经存在
    • ✔ 所以 Bob 必须先锁 Token_B 才能获得 S
    • ✔ Alice 永远不会提前泄露 S

    1. 编写测试的ERC20代币合约和HTLC合约

    test_token_sol_code = \”\”\”
    // SPDX-License-Identifier: MIT
    pragma solidity >=0.8.0 <0.9.0;

    import \”@openzeppelin/contracts/token/ERC20/ERC20.sol\”;

    // ERC20 token used for unit testing
    contract TestToken is ERC20 {
    constructor(
    string memory name,
    string memory symbol,
    uint256 initialSupply
    ) ERC20(name, symbol) {
    _mint(msg.sender, initialSupply);
    }
    }
    \”\”\”

    htlc_sol_code = \”\”\”
    // SPDX-License-Identifier: MIT
    pragma solidity >=0.8.0 <0.9.0;

    import \”@openzeppelin/contracts/token/ERC20/ERC20.sol\”;

    contract HTLC {
    struct Lock {
    uint unlockTime;
    uint amount;
    address tokenAddress;
    address senderAddress;
    address receiverAddress;
    }

    mapping(bytes32 => Lock) public locks;

    event Claimed(
    bytes preImage,
    bytes32 hashValue,
    uint when,
    uint amount,
    address tokenAddress,
    address senderAddress,
    address receiverAddress
    );

    event Locked(
    bytes32 hashValue,
    uint when,
    uint amount,
    address tokenAddress,
    address senderAddress,
    address receiverAddress
    );

    event Retaken(
    bytes32 hashValue,
    uint when,
    uint amount,
    address tokenAddress,
    address senderAddress,
    address receiverAddress
    );

    function claim(bytes calldata preImage) external {
    bytes32 hashValue = keccak256(preImage);
    Lock storage l = locks[hashValue];
    uint amount = l.amount;
    require(amount > 0, \”HTLC: not a valid pre-image for any hash\”);

    require(block.timestamp < l.unlockTime, \”HTLC: can only claim before the unlock time\”);
    address receiverAddress = l.receiverAddress;
    require(msg.sender == receiverAddress, \”HTLC: only the receiver can claim\”);

    IERC20 erc20 = IERC20(l.tokenAddress);
    delete locks[hashValue];
    require(erc20.transfer(receiverAddress, amount), \”HTLC: erc20 transfer must be successful\”);

    emit Claimed({
    preImage: preImage,
    hashValue: hashValue,
    amount: l.amount,
    when: block.timestamp,
    tokenAddress: l.tokenAddress,
    senderAddress: l.senderAddress,
    receiverAddress: l.receiverAddress
    });
    }

    function lock(
    bytes32 hashValue,
    uint unlockTime,
    uint amount,
    address tokenAddress,
    address receiverAddress
    ) external {
    require(locks[hashValue].amount == 0, \”HTLC: lock cannot already exist for the same hash value\”);
    require(amount > 0, \”HTLC: cannot lock zero tokens\”);

    locks[hashValue] = Lock({
    unlockTime: unlockTime,
    amount: amount,
    tokenAddress: tokenAddress,
    senderAddress: msg.sender,
    receiverAddress: receiverAddress
    });

    IERC20 erc20 = IERC20(tokenAddress);

    require(
    erc20.transferFrom(msg.sender, address(this), amount),
    \”HTLC: erc20 transfer for locking must be successful\”
    );
    }

    function retake(bytes32 hashValue) external {
    Lock storage l = locks[hashValue];
    uint amount = l.amount;
    require(amount > 0, \”HTLC: no lock exists for the given hash\”);

    require(block.timestamp >= l.unlockTime, \”HTLC: can only retake on or after the unlock time\”);
    address senderAddress = l.senderAddress;
    require(msg.sender == senderAddress, \”HTLC: only the sender can retake\”);

    IERC20 erc20 = IERC20(l.tokenAddress);
    delete locks[hashValue];
    require(erc20.transfer(senderAddress, amount), \”HTLC: erc20 transfer must be successful\”);

    emit Retaken({
    hashValue: hashValue,
    amount: l.amount,
    when: block.timestamp,
    tokenAddress: l.tokenAddress,
    senderAddress: l.senderAddress,
    receiverAddress: l.receiverAddress
    });
    }
    }
    \”\”\”

    2. 创建两个测试账户

    from web3 import Web3

    w3 = Web3(Web3.EthereumTesterProvider())
    assert w3.is_connected(), \”无法连接到Ethereum节点\”

    Alice = w3.eth

    赞(0)
    未经允许不得转载:171主机测评 » web3的跨链(HTLC原子交换 )python实现
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址