哈希时间锁定跨链方案在ethereum和fabric间的探索实践

哈希时间锁定跨链

跨链需求

随着区块链技术的发展,各种具有不同特点,不同应用场景的区块链比如比特币、以太坊等公链以及私链和联盟链大量共存,它们之间相互独立,进行数据通信和价值转移仍面临挑战,区块链孤岛现象十分严重。为了解决区块链孤岛,实现链与链之间互联互通,进行价值转移,就必须实现跨链技术。就连银保监会陈伟刚也曾说过:“区块链的跨链需求会越来越多,因为区块链之一的联盟链,只是在自己的行业里应用,效益就得不到最优,更多的是行业与行业之间的联盟链跨链,所以跨链需求会成为区块链的一个拓展方向”。

目前,主流的跨链技术方案主要有三种,分别是哈希时间锁定、公证人机制和中继链方式。本篇主要分析实现哈希时间锁定跨链技术方案。哈希时间锁定本质是一种智能合约,其最先出现于2013年的BitcoinTalk论坛的一次讨论中,最早在闪电网络中实现。

哈希时间锁简单介绍

哈希时间锁是基于hash锁和时间锁来实现的。

hash锁

hash算法是一个可以将任意长度的输入内容以一个固定长度输出的算法,输入内容称为原始值,输出内容称为hash值,hash算法可以保证:

  • a. 输入内容一致的情况下,输出内容也是相同的;
  • b. 从输出内容不能反向推导出输入内容。
    基于hash算法的特点,我们可以将hash值看成一把锁,而原始值则是唯一可以解开这把锁的钥匙。

时间锁

时间锁比较简单,时间锁上会记录一个时间点,即使有正确的钥匙,也必须在此时间点前才能打开,过时则无法打开。介绍完哈希时间锁的以后,我们来看下哈希时间锁是如果实现跨链的。

哈希时间锁定原理

哈希时间锁定是基于哈希锁和时间锁来实现跨链。假如有两条区块链ChainA和ChainB,每个链的原生资产分别为a、b,两个用户Alice和Bob,Alice有资产a,Bob有资产b,Alice与Bob想交换资产,那么他们可以使用以下的方式来实现:

1、Alice选取一个秘密随机数S,计算出S的哈希值H(S),Alice将H(S)发送给Bob,然后Alice指定一个时间点T1。接着Alice在ChainA上创建资产锁定合约,合约执行以下步骤:

  • 使用hash值H(S)将自己在ChainA上的资产a加上哈希锁;
  • 使用时间点T1将自己在ChainA上资产a加上时间锁;
    只有获得S并且在时间点T1之前才将资产a转移走,如果到时间点T1后仍未解锁,则允许撤销锁定,且不会发生资产转移。

2、Alice将H(S)发送给Bob,Bob基于H(S)和一个小于T1的时间点T2在ChainB上创建资产锁定合约,合约执行以下步骤:

  • 使用hash值H(S)将自己在ChainB上的资产b加上哈希锁;
  • 使用时间点T2将自己在ChainB上资产b加上时间锁;
    只有拥有S并且在时间点T2之前才将资产b转移走,如果到时间点T2后仍未解锁,则b资产超时锁定。
    3、Alice使用S在时间点T2前调用ChainB上的智能合约解锁资产b,并将资产转移给自己。
    4、因为Alice解锁了资产b,所以Bob也获得S的值,Bob使用S在时间点T1前解锁资产a,并将资产转移给自己。

通过以上的过程Alice和Bob就使用哈希时间锁在两条链上完成了资产转移。

ChainA UserA UserB ChainB 生成秘密随机数h,并计算其哈希值H。 发送H。 使用H和T1锁定资产a。 使用H和T2锁定资产b。 使用h解锁资产b。 使用h解锁资产a。 T2时刻,如果资产b未被解锁,则超时锁定,UserB退回资产b。 T1时刻,如果资产a未被解锁,则超时锁定,UserA退回资产a。 ChainA UserA UserB ChainB

适用场景

虽然哈希时间锁定可以完成跨链,但它仅适用于不同区块链间的资产价值交换,且使用它的区块链网络需要如下三点:第一必须要有账户资产,第二必须通过可编程智能合约建立信任,第三交易双方必须在两种区块链网络中拥有各自的资产托管账户。哈希时间锁作为跨链资产交换解决方案,目前应用于WeCross、闪电网络、Interledger、雷电网络、Sprites 通道等。除此之外还在证券结算、去中心化交易所以及央行数字货币跨境支付得到广泛引用。

哈希时间锁定在Fabric与Ethereum间跨链探索

因为哈希时间锁定不适用于fabric等没有资产的联盟链,所以我们需要先让我们的fabric网络拥有账户资产,为此我们需要实现账户资产链码,再实现哈希时间锁定链码,所以fabric网络需要账户资产和哈希时间锁定两种链码。对于以太坊,我们只需要编写一个哈希时间锁定合约即可。

Fabric上面的实现

账户资产链码
type Account struct {
    Address  string      `json:"address"`     // 账户地址
	Amount   uint64      `json:"amount"`      // 账户资产余额
	Passwd   string      `json:"passwd"`      // 账户密码
	Sequence uint64      `json:"sequence"`    // 账户序列号,用于创建中间账户
	Type     int         `json:"type"`        // 账户类型,0为中间账户1为普通账户
	TransferTo [2]string `json:"transfer_to"` // 中间账户资产能够转出的地址,为哈希交易的双方地址
}

Account结构体为账户资产模型,其中Amount为资产金额,Sequence序列号主要用于创建中间账户,Type为账户类型,0为中间账户,1为普通账户,TransferTo表示该账户资产能够转入的账户地址,当为普通账户时为空,为中间账户时是hash交易的双方地址。

Account提供一个转账方法:

/**
	*to:接收者账户
	*amount:转账金额
*/
func (from *Account) Transfer(stub shim.ChaincodeStubInterface, to *Account, amount uint64) error {
	sendKey := fmt.Sprintf(AccountPrefix, from.Address)
	from.Amount = safeSub(from.Amount, amount)
	receiverKey := fmt.Sprintf(AccountPrefix, to.Address)
	to.Amount = safeAdd(to.Amount, amount)
    ...
}

账户结构体的转账方法,发送者账户减少资产接收者账户增加资产,在分别保存发送者账户和接收者账户。

账户资产链码主要提供4个接口,分别是中间用户注册用户注册资产转账用户查询

/**
	*args[0]:address,中间账户地址。
	*args[1]:passwd,中间账户的密码或者密码的哈希值。
	*args[2]:flag,标志位,为hash表示为passwd为中间账户的密码的哈希值。
	*args[3]:sender,哈希时间锁交易的发送者。
	*args[4]:receiver,哈希时间锁交易的接收者。
*/
func (a *AccountAssert) createMidAccount(stub shim.ChaincodeStubInterface, args []string) pb.Response {
    ...
    // 计算passwd的哈希值,以存储
    if flag == "hash" {
		pw = passwd
	} else {
		sha256Passwd := sha256.Sum256([]byte(passwd))
		pw = hex.EncodeToString(sha256Passwd[:])
	}
    transferTo := [2]string{sender, receiver}
	acc := Account{
		Address:  address,
		Amount:   0,
		Passwd:   pw,
		Sequence: 0,
		Type: MidAccount,
		TransferTo: transferTo,
	}
    ...
}

中间用户注册需要5个参数,账户地址address、账户密码passwd、账户标志位flag、哈希时间锁交易发送者sender和哈希时间锁交易接收者receiver。中间账户的密码就是哈希时间锁定交易里面的哈希原像,因为可能为后创建哈希时间锁的账户只能知道哈希值,所以通过标志位flag区分passwd是哈希原像还是哈希值。

/**
	*args[0]:address,普通账户地址。
	*args[1]:passwd,普通账户的密码。
*/
func (a *AccountAssert) createAccount(stub shim.ChaincodeStubInterface, args []string) pb.Response {
    ...
    passwd := args[1]
    // 计算passwd的哈希值,以存储
	sha256Passwd := sha256.Sum256([]byte(passwd))
	acc := Account{
		Address:  address,
		Amount:   0,
		Passwd:   hex.EncodeToString(sha256Passwd[:]),
		Sequence: 0,
		Type: GenAccount,
		TransferTo: [2]string{}, // 普通账户,TransferTo为空值
	}
    ...
}

用户注册需要传入地址address,密码passwd,密码存储的是哈希值。

/**
	*args[0]:from,交易转出方地址。
	*args[1]:to,交易转入方地址。
	*args[2]:amount,转账金额。
	*args[3]:passwd,交易转出方账户密码。
*/
func (a *Account) transfer(stub shim.ChaincodeStubInterface, args []string) pb.Response {
    ...
    // 得到交易转出方账户
    fromKey := fmt.Sprintf(AccountPrefix, from)
	senderInfo, err := stub.GetState(fromKey)
    sender := &Account{}
	err = json.Unmarshal(senderInfo, sender)
	...
    // 验证交易转出方账户密码是否正确
    sha256Passwd := sha256.Sum256([]byte(passwd))
	if strings.Compare(sender.Passwd, hex.EncodeToString(sha256Passwd[:])) != 0 {
		return shim.Error("sender account passwd is error")
	}
    ...
    // 交易转出方是中间账户,判断转入方地址是否正确,保证中间账户资产的安全性
    if sender.Type == MidAccount {
		if sender.TransferTo[0] != to && sender.TransferTo[1] != to {
			return shim.Error("mid account not can transfer to other account")
		}
	}
    ...
    // 调用账户的转账方法执行转账操作
    err = sender.Transfer(stub, receiver, amount)
    ...
}

资产转账需要传入发送者from,接收者to,金额amount和发送者密码passwd四个参数。首先判断发送者是否存在,再判断账户密码是否正确,接着再判断接收者是否存在以及发送者金额是否充足,当发送者是中间账户还要判断接收者地址的正确性,这些都校验通过最后在在发送者账户执行减法操作,在接收者账户执行加法操作,以完成转账。

/**
	*args[0]:address,交易转出方地址。
*/
func (a *AccountAssert) query(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	...
    key := fmt.Sprintf(AccountPrefix, address)
	accByte, err := stub.GetState(key)
    ...
}

账户查询只需要传入账户地址,账户存在就返回账户信息,不存在就返回账户不存在。

htlc链码
type HTLC struct {
	Sender      string    `json:"sender"`
	Receiver    string    `json:"receiver"`
	Amount      uint64    `json:"amount"`
	HashValue   string    `json:"hash_value"`
	TimeLock    int64     `json:"time_lock"`
	PreImage    string    `json:"pre_image"`
	LockAddress string    `json:"lock_address"`
	State       HTLCState `json:"state"`
}

HTLC结构体是哈希时间锁定的交易数据结构,HashValue为PreImage的sha256哈希值,LockAddress就是用户注册的中间账户地址,PreImage为该中间账户的密码,状态State共分为已锁定HashLOCK、已领取Received和已退款Refund共三种。

HTLC链码主要有创建中间账户、创建哈希时间锁定、领取资产、退回资产和查询哈希时间锁定信息共5个功能。

/**
	*args[0]:sender,哈希时间锁定交易发送者。
	*args[1]:preImage,哈希时间锁定交易的哈希原像或者是原像的哈希值。
	*args[2]:flag,标志位,为hash表示preImage为原像的哈希值。
	*args[3]:receiver,哈希时间锁定交易接收者。
*/
func (h *HTLCChaincode) createMidAccount(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	...
    // 中间账户地址以发送者地址+发送者的序列号方式创建的
    midAddress := senderAccount.Address + uint64ToString(senderAccount.Sequence)
    ...
    // 调用账号资产链码的创建中间账户接口
    trans = [][]byte{[]byte("registermidaccount"), []byte(midAddress), []byte(preImage), []byte(flag), []byte(sender), []byte(receiver)}
	resPonse = stub.InvokeChaincode(AccountChainCodeName, trans, AccountChainCodeChannel)
	if resPonse.Status != shim.OK {
		return shim.Error("craete htlc register mid account error: " + resPonse.Message)
	}
	...
    // 返回中间账户地址和哈希时间锁定交易的哈希值
    respon := ResponseMidAccount{}
	respon.Address = midAddress
	if flag == "hash" {
		respon.Hash = preImage
	} else {
		hashByte := sha256.Sum256([]byte(preImage))
		respon.Hash = hex.EncodeToString(hashByte[:])
	}
	responByte, err := json.Marshal(respon)
    ...
	return shim.Success(responByte)
}

创建中间账户需要传入交易发送者地址,哈希原像(哈希原像为中间账户的密码)或者哈希值,标志位和交易接收者地址。它主要调用账户资产链码的中间用户注册接口,返回的是中间账户地址和哈希时间锁定的哈希值。

/**
	*args[0]:sender,哈希时间锁定交易发送者地址。
	*args[1]:receiver,哈希时间锁定交易接收者地址。
	*args[2]:amount,哈希时间锁定交易金额。
	*args[3]:timelock,哈希时间锁定交易的时间锁。
	*args[4]:hashValue,哈希时间锁交易哈希值。
	*args[5]:passwd,哈希时间锁定交易发送者账户密码。
	*args[6]:midaddress,哈希时间锁定交易的锁定地址。
*/
func (h *HTLCChaincode) createHash(stub shim.ChaincodeStubInterface, args []string) pb.Response {
    ...
    // 调用账户资产链码的转账接口,完成资产从发送者账户到锁定账户的转移
    trans = [][]byte{[]byte("transfer"), []byte(sender), []byte(midaddress), []byte(amountStr), []byte(passwd)}
	resPonse = stub.InvokeChaincode(AccountChainCodeName, trans, AccountChainCodeChannel)
	if resPonse.Status != shim.OK {
		return shim.Error("craete htlc transfer mid  account error:" + resPonse.Message)
	}
    ...
    htlc := HTLC{
		Sender:      sender,
		Receiver:    receive,
		Amount:      amount,
		HashValue:   hashValue,
		TimeLock:    timeLock,
		PreImage:    "", // 先设置为空,在对方领取资产的时候在给值
		LockAddress: midaddress,
		State:       HashLOCK,
	}
    htlcByte, err := json.Marshal(htlc)
	idByte := sha256.Sum256(htlcByte)
	id := hex.EncodeToString(idByte[:])
    ...
    // 返回id
    return shim.Success([]byte(id))
    ...
}

创建哈希时间锁定交易需要发送者地址、接收者地址、转账数额、时间锁、哈希值、发送者账户密码和中间账户地址。哈希值和中间账户地址就是上一步创建中间账户时的返回值。它主要调用账户资产链码的资产转账接口,完成资产从发送者账户到中间账户的转移,返回哈希时间锁定交易id。

/**
	*args[0]:id,哈希时间锁定交易id。
	*args[1]:preImage,哈希时间锁定的哈希原像。
*/
func (h *HTLCChaincode) withdraw(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	...
    // 状态必须已锁定
    if htlc.State != HashLOCK {
		return shim.Error("this htlc transaction state is error")
	}
	// 时间锁不能过期
	if htlc.TimeLock < time.Now().Unix() {
		return shim.Error("time is expirate")
	}
	// 调用账户资产链码的转账接口,完成资产从中间账户到交易接收者账户的转移
	trans := [][]byte{[]byte("transfer"), []byte(htlc.LockAddress), []byte(htlc.Receiver), []byte(uint64ToString(htlc.Amount)), []byte(preImage)}
	resPonse := stub.InvokeChaincode(AccountChainCodeName, trans, AccountChainCodeChannel)
	...
	// 对哈希时间锁定交易的哈希原像进行赋值并把状态修改成已领取
	htlc.PreImage = preImage
	htlc.State = Received
    ...
}

领取资产需要哈希时间锁定交易id和哈希原像(中间账户密码),首先判断该哈希时间锁定交易是否存在,再判断哈希时间锁定状态是否是已锁定,然后判断时间锁是否超时,都检查通过之后再调用账户链码资产的资产转移接口,完成资产从中间账户到接收者账户的转移并更新哈希原像及状态。

/**
	*args[0]:id,哈希时间锁定交易id。
	*args[1]:preImage,哈希时间锁定的哈希原像。
*/
func (h *HTLCChaincode) refund(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	...
    // 时间锁需要过期
    if htlc.TimeLock > time.Now().Unix() {
		return shim.Error("time is not expirate")
	}
	// 状态要已锁定
	if htlc.State != HashLOCK {
		return shim.Error("this htlc transaction state is error")
	}
	// 调用账户资产链码的转账接口,完成资产从中间账户到交易发送者账户的转移
	trans := [][]byte{[]byte("transfer"), []byte(htlc.LockAddress), []byte(htlc.Sender), []byte(uint64ToString(htlc.Amount)), []byte(preImage)}
	resPonse := stub.InvokeChaincode(AccountChainCodeName, trans, AccountChainCodeChannel)
	...
	// 更新状态为已退款
	htlc.State = Refund
    ...
}

退回资产是通过哈希时间锁定交易id和哈希原像,检查交易是否过期且状态是否为已锁定,检查通过再调用账户资产链码的资产转移接口,完成资产从中间账户到交易发送者账户的转移,之后再更新状态为已退款。

/**
	*args[0]:id,哈希时间锁定交易id。
*/
func (h *HTLCChaincode) query(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	...
    // 根据id查询对应的HTLC并返回
    key := fmt.Sprintf(HTLCPrefix, id)
	htlcByte, err := stub.GetState(key)
    ...
    return shim.Success(htlcByte)
	...
}

查询哈希时间锁定信息通过哈希时间锁定交易id来查看该笔哈希时间锁定交易的信息。

Ethereum上面的实现

以太坊上的哈希时间锁定合约主要包含以下4个功能:资产锁定、提取资产、退回资产以及查询锁定信息。

/**
    * @dev 发送者设置哈希时间锁来存储ETH以及其锁定时间。
    *
    * @param _receiver ETH接收者。
    * @param _hashlock 基于sha256的哈希时间锁。
    * @param _timelock 过期是的时间戳,在此之间之后若ETH还未被接收者提取,可以被发送者取回。
    * @return htlcId 资产被锁定的在的HTLC的Id。之后的调用会需要。
*/
function newHTLC(address payable _receiver, bytes32 _hashlock, uint _timelock)
external
payable
fundsSent
futureTimelock(_timelock)
returns (bytes32 htlcId)
{
htlcId = sha256(abi.encodePacked(msg.sender, _receiver, msg.value, _hashlock, _timelock));
 
// Reject if a contract already exists with the same parameters. The
// sender must change one of these parameters to create a new distinct
// contract.
if (haveContract(htlcId))
revert("Contract already exists");
contracts[htlcId] = LockHTLC(msg.sender, _receiver, msg.value, _hashlock, _timelock, false, false, '0x0');
 
emit LogHTLCNew(htlcId, msg.sender, _receiver, msg.value, _hashlock, _timelock);
}

资产锁定操作需要接收者地址、哈希值、时间值、以及锁定的资产(ETH)数量,其中哈希值和时间值作为资产锁定的约束,即相关资产接收者需要在此时间值代表的时间之前提供正确的哈希原像才可以提取锁定的资产。资产发送者执行此操作会生成资产锁定id,并把资产发送者、接收者、锁定资产数量、哈希值、时间值、以及锁定id记录在日志中。

 /**
     * @dev 接收者一旦知道时间锁原像,会调用此方法提取锁定资产。
     *
     * @param _htlcId HTLC的Id。
     * @param _preimage 哈希锁原像,sha256(_preimage) 等于哈希锁。
     * @return bool 成功返回true。
*/
function withdraw(bytes32 _htlcId, bytes calldata _preimage)
external
contractExists(_htlcId)
hashlockMatches(_htlcId, _preimage)
withdrawable(_htlcId)
returns (bool)
{
  LockHTLC storage c = contracts[_htlcId];
  c.preimage = _preimage;
  c.withdrawn = true;
  c.receiver.transfer(c.amount);
  emit LogHTLCWithdraw(_htlcId);
  return true;
}

提取资产操作需要资产锁定id以及哈希原像,资产接收者在限定时间内执行此操作可以提取资产并且之前的资产锁定日志状态会被更新,资产提取也会记录在日志中;

/**
     * @dev 如果时间锁过期,发送者调用此方法取回锁定的资产。
     *
     * @param _htlcId 锁定资产的HTLC的Id
     * @return bool 成功返回true。
*/
function refund(bytes32 _htlcId)
external
contractExists(_htlcId)
refundable(_htlcId)
returns (bool)
{
  LockHTLC storage c = contracts[_htlcId];
  c.refunded = true;
  c.sender.transfer(c.amount);
  emit LogHTLCRefund(_htlcId);
  return true;
}

退回资产操作需要资产锁定id,资产发送者在锁定时间过期后执行此操作会退回锁定的资产到自身账户,相关的退款操作也会记录在日志中。

/**
     * @dev 获取HTLC的细节。
     * @param _htlcId HTLC的Id。
     * @return 所有LockHTLC的参数。
*/
function getContract(bytes32 _htlcId)
public
view
returns (
address sender,
address receiver,
uint amount,
bytes32 hashlock,
uint timelock,
bool withdrawn,
bool refunded,
bytes memory preimage
)
{
  if (haveContract(_htlcId) == false){
    bytes memory pi = '0x0';
    return (address(0), address(0), 0, 0, 0, false, false, pi);
  }
  LockHTLC storage c = contracts[_htlcId];
  return (
    c.sender,
    c.receiver,
    c.amount,
    c.hashlock,
    c.timelock,
    c.withdrawn,
    c.refunded,
    c.preimage
  );
}

查询哈希锁定信息需要资产锁定id并返回该笔资产锁定信息。

跨链流程步骤

fabric User1 User2 ethereum 1、User1通过哈希原像h创建中间账户,得到中间账户地址和哈希值H。 2、User1通过H和中间账户地址,创建哈希锁定交易,返回交易id1。 User1发送交易id1给User2 User2通过交易id1查询fabric上哈希时间锁定交易,得到哈希H和中间账户地址midAddress以及时间锁T。 User2通过中间账户地址midAddress确认账户金额。 3、User2使用H和1/2T创建哈希时间锁定交易,得到交易id2。 User2发送交易id2给User1 4、User1使用h和id2去ethereum网络取回资产。 User2通过id2去ethereum网络查看,得到h。 5、User2通过h和id1去fabric网络取回资产,交易完成。 6、如果User1没有去ethereum领取资产,1/2T时刻后,User2退回资产。 7、T时刻,User1退回资产。 fabric User1 User2 ethereum

总结

哈希时间锁定的应用场景只适用于资产或者Token的转移,比较适用于公链带有原生Token的领域,对于不包含资产托管账户(例如Fabric)的区块链需要借助智能合约来构建账户概念。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

左耳朵明星

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

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

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

打赏作者

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

抵扣说明:

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

余额充值