在探索学习区块链扩容方面的技术时,了解到跨链是区块链二层扩容的重要部分,而实现跨链的技术主要有:公证人技术、中继/侧链技术、哈希时间锁定技术。接下来,我们将在这篇文章中详细介绍哈希时间锁定技术的原理及实现等。
故事
从前有一对分隔异地的情侣,他们用写信互诉衷肠,不过他们担心邮递员会偷窥信中的情话。男主想到了一个好点子,他让邮递员将一个盒子投递给女主,盒子里放着一把打开的锁,女主心领神会,写好书信,放入盒中,用那把锁锁住盒子。男主收到盒子后,用唯一的钥匙打开了盒子,读懂了女主的心情。他们完成了安全的书信联系。这就是下文探讨的哈希时间锁定的基本原理。
概述
哈希时间锁定(Hash Time Lock Contract),也被称为哈希锁定,其本质是一种智能合约。这一概念最早出现在 2013 年 BitcoinTalk 论坛里的一次讨论中,最早在比特币的闪电网络中得到应用和实现,且该机制来源于 Atomic Swap。
在闪电网络的支付通道中,它是通过哈希时间锁定智能合约来实现的。也就是限时转账,通过该智能合约,双方约定转账方先冻结一笔钱,并通过哈希锁将发起方的交易代币锁定,如果在规定时间内有人能够提供之前生成支付的加密证明,并且与之前约定的哈希值一致,交易即可完成。
可见在哈希时间锁定机制中包含有哈希锁和时间锁两把锁,通过这两把锁的巧妙配合来保证区块链上交易的原子性,即只有满足一定的时间条件和哈希条件才能达成该交易,否则就什么也不会发生。
那么拆开来看,何为哈希锁以及时间锁呢?
哈希锁
所谓哈希锁,即通过哈希值对一次交易加锁,该锁只能由这个哈希值的原值进行解锁。例如:字符串 “key” 经过哈希函数求值之后得到的值为 “1se@&#^”,那么通过 “1se@&#^” 加锁后的交易,在不考虑哈希碰撞的情况下,就只能由原值 “key” 进行解锁。
时间锁
所谓时间锁,即在规定的时间内才可以开锁。例如:通过时间锁规定开锁的有效时间为 1 个小时,开锁的条件为输入正确的哈希值的原值。那么要想解锁这个时间锁的唯一条件就是在 1 个小时内输入正确的哈希值原值,若在 1 个小时后进行解锁,尽管哈希值输入正确了,该时间锁仍然不会被解锁。
接收方只能在规定时间内凭借哈希值的原值来解锁这次交易,在这段时间内,尽管发送方知道哈希值的原值,但他仍然无权解锁,这样就限制了发送方在给接收方共享了秘钥后自己提前退款这样的作恶行为。
同样的,若在有效时间内,接收方没有用哈希值原值进行解锁,那么在有效时间过了之后,尽管接收方得到了哈希值原值,他也不可能解锁成功,因为超时后只能由发送方解锁这边交易了,即这笔资产会退回到发送方账户中。
通过哈希锁和时间锁的巧妙配合,就可以对资产的发送方和接收方形成相互制约,同时保证资产的交换要么发生,要么不发生,最终保证了该笔交易的原子性。
解决了什么问题
通过传统的中心化交易所进行资产的交易时,通常我们需要先将资产交给交易所,再由交易所进行撮合,最终促成交易的达成。但由于这样的交易所通常是中心化的交易所,因此必然会存在对交易所的信任问题,这就带来了一定的交易风险,还会产生较高的手续费。
而通过哈希时间锁定机制进行资产交易时,可以通过哈希锁和时间锁的双重保障,对资产的发送方和接收方都形成制约,从而促进交易的发生。若双方按照哈希时间锁的规则进行交易,则交易就可达成;若交易失败,实际上在区块链上并未发生任何的资产交换,也就无需支付额外的交易费用了。因此,通过哈希时间锁定机制可以有效保证跨链交易的原子性,而不需第三方公证人进行信任担保。
单向哈希时间锁定
所谓单向哈希时间锁定,指的是资产的发送方随机生成一个秘钥 x,并通过一个哈希函数 h() 得到 x 对应的哈希值 h(x),然后构建一个智能合约,并在合约中规定,只有资产接收方用 x 来解锁该合约才可以得到合约中锁定的资产,再设定一个超时时间,并规定只有在该超时时间内接收方通过秘钥 x 来解锁才有效,若超过超时时间,只能由资产的发送方才能解锁并退回资产。
举个例子,就好像我在一栋大楼的保险箱里放了一些资产,我见到你之后把那个保险箱的钥匙给你,并告诉你:你只有在 1 个小时之内去某个位置找到这个保险箱,就可以解锁取走这些资产,否则 1 个小时之后,尽管你有钥匙,也无法取走保险箱里的资产,那时只有我有权限解锁该保险箱取走该资产了。
接下来,我们就以以太坊中的智能合约语言 Solidity 为例,看一下哈希时间锁定的具体实现:
首先我们需要对哈希时间锁定合约进行定义,我们需要设计该合约的数据结构,以及其对应的 3 个最主要的方法,分别为:合约的构建、取出资产、退回资产。
contract HashedTimelock {
...
struct LockContract {
address payable sender;
address payable receiver;
uint amount;
bytes32 hashlock; // use sha256 hash function
uint timelock; // UNIX timestamp seconds - locked UNTIL this time
bool withdraw;
bool refunded;
bytes32 preimage; // it's secret, sha256(_preimage) should equal to hashlock
}
...
function newContract (address payable _receiver, bytes32 _hashlock, uint _timelock) ... {
...
}
function withdraw(bytes32 _contractId, bytes32 _preimage) ... {
...
}
function refund(bytes32 _contractId) ... {
...
}
}
可以看到在 LockContract 的定义中,我们规定了该笔合约的发送方(sender)和接收方(receiver),即该笔合约中锁定的资产只能被发送方或接收方取出;接下来定义了哈希锁(hashlock)和时间锁(timelock),哈希锁对应的秘钥为 preimage,这里我们若采用 sha256 的加密方式,那么 sha256(preimage) 就一定等于 hashlock;除此之外我们还定义了该合约中锁定的资产数量 amount,以及该合约对应的状态,即是否被取出(withdraw)、是否被退回(refunded)。
contract HashedTimelock {
...
modifier fundsSent() {
require(msg.value > 0, "msg.value must be > 0");
_;
}
modifier futureTimelock(uint _time) {
// 唯一的要求就是 timelock 时间锁指定的时间要在最后一个区块产生的时间(now)之后
require(_time > now, "timelock time must be in the future");
_;
}
function newContract(address payable _receiver, bytes32 _hashlock, uint _timelock)
external payable fundsSent futureTimelock(_timelock) returns (bytes32 contractId)
{
contractId = sha256(abi.encodePacked(msg.sender, _receiver, msg.value, _hashlock, _timelock));
// 若具有相同参数的合约已经存在,这次新建合约的请求就会被拒绝。
// sender 只有更改其中的一个参数,以创建一个不同的合约。
if (haveContract(contractId))
revert();
contracts[contractId] = LockContract( ... );
}
...
function