赞
踩
随着区块链技术的发展,各种具有不同特点,不同应用场景的区块链比如比特币、以太坊等公链以及私链和联盟链大量共存,它们之间相互独立,进行数据通信和价值转移仍面临挑战,区块链孤岛现象十分严重。为了解决区块链孤岛,实现链与链之间互联互通,进行价值转移,就必须实现跨链技术。就连银保监会陈伟刚也曾说过:“区块链的跨链需求会越来越多,因为区块链之一的联盟链,只是在自己的行业里应用,效益就得不到最优,更多的是行业与行业之间的联盟链跨链,所以跨链需求会成为区块链的一个拓展方向”。
目前,主流的跨链技术方案主要有三种,分别是哈希时间锁定、公证人机制和中继链方式。本篇主要分析实现哈希时间锁定跨链技术方案。哈希时间锁定本质是一种智能合约,其最先出现于2013年的BitcoinTalk论坛的一次讨论中,最早在闪电网络中实现。
哈希时间锁是基于hash锁和时间锁来实现的。
hash锁
hash算法是一个可以将任意长度的输入内容以一个固定长度输出的算法,输入内容称为原始值,输出内容称为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上创建资产锁定合约,合约执行以下步骤:
2、Alice将H(S)发送给Bob,Bob基于H(S)和一个小于T1的时间点T2在ChainB上创建资产锁定合约,合约执行以下步骤:
通过以上的过程Alice和Bob就使用哈希时间锁在两条链上完成了资产转移。
虽然哈希时间锁定可以完成跨链,但它仅适用于不同区块链间的资产价值交换,且使用它的区块链网络需要如下三点:第一必须要有账户资产,第二必须通过可编程智能合约建立信任,第三交易双方必须在两种区块链网络中拥有各自的资产托管账户。哈希时间锁作为跨链资产交换解决方案,目前应用于WeCross、闪电网络、Interledger、雷电网络、Sprites 通道等。除此之外还在证券结算、去中心化交易所以及央行数字货币跨境支付得到广泛引用。
因为哈希时间锁定不适用于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)
...
}
账户查询只需要传入账户地址,账户存在就返回账户信息,不存在就返回账户不存在。
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来查看该笔哈希时间锁定交易的信息。
以太坊上的哈希时间锁定合约主要包含以下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并返回该笔资产锁定信息。
哈希时间锁定的应用场景只适用于资产或者Token的转移,比较适用于公链带有原生Token的领域,对于不包含资产托管账户(例如Fabric)的区块链需要借助智能合约来构建账户概念。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。