本文介绍了基于哈希时间锁的跨链资产交换,通过一套自动化Demo展示了# 基于

适用场景

​ 虽然哈希时间锁定可以完成跨链,但它仅适用于不同

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 error") } ... // 交易转出方是中间账户,判断转入方地址是否正确,保证中间账户资产的安全性 if sender.Type == MidAccount { if sender.TransferTo[0] != to && sender.TransferTo[1] != to { return shim.Error("receiving account doesn't match") } } ... // 调用账户的转账方法执行转账操作 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结构体是哈希时间锁定的交易数据结构,HashValuePreImage的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("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("create 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("htlc transaction state error") } // 时间锁不能过期 if htlc.TimeLock < time.Now().Unix() { return shim.Error("time has expirated") } // 调用账户资产链码的转账接口,完成资产从中间账户到交易接收者账户的转移 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("cannot refund assets before expiration") } // 状态要已锁定 if htlc.State != HashLOCK { return shim.Error("htlc transaction state 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上面的实现

资产锁定操作需要接收者地址、哈希值、时间值、以及锁定的资产(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与Ethereum基于HTLC的跨链流程

HTLC跨链

跨链演示

跨链演示背景说明:假设有两个用户Alice和Bob,Alice在fabric网络上面有1000个资产,Bob没有资产,Alice在

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注