Anyswap

作者: 雪落无留痕 | 来源:发表于2021-12-20 11:31 被阅读0次

门限签名

通过(t, n)门限签名,即可将一个秘密(私钥)分成n份,任意 t+1个成员可以通过多轮交互,在不重构私钥的情况下,生成一个有效的签名。

目前主要采用GG18门限签名方案,其密码生成阶段需要5轮交互,签名阶段需要10轮交互。

Anyswap

Anyswap是去中心化的跨链协议,基于Fusion DCRM技术。Fushion的DCRM(Distributed Control Right Management) 技术主要用来解决跨链的互操作性,主要采用基于MPC的ECDSA和EdDSA门限签名技术, 实现分布式密钥生成和交易签名。

Anyswap 是Fushion的一个由DCRM节点组成的侧链网络,最开始有5个节点,后续将扩展到10个以上。

技术架构

跨链桥

跨链桥采用的是铸造-销毁模型,以BNB实现BSC -> OEC 跨链为例:

  1. 用户在BSC上发起转账交易:https://bscscan.com/tx/0x159ca1687828dafc5de4439c4192813911dc54f87f0c8c3935702de63d4182ef

  2. mpc地址: 0x63a3d28bb9187809553dd16981c73f498b6b2687在OEC发起Swapin交易: https://www.oklink.com/oec/tx/0xCFBC6859EEA694797FA6ED6C4052FC4C017CCF06F32C6791E2BF889A34E39A93

    function Swapin(bytes32 txhash, address account, uint256 amount) public onlyAuth returns (bool) {
        _mint(account, amount);
        emit LogSwapin(txhash, account, amount);
        return true;
    }

合约信息: https://www.oklink.com/oec/address/0x218c3c3d49d0e7b37aff0d8bb079de36ae61a4c0

反向操作OEC -> BSC:

  1. 用户在OEC 上发起交易,调用Swapouthttps://www.oklink.com/oec/address/0x218c3c3d49d0e7b37aff0d8bb079de36ae61a4c0

    function Swapout(uint256 amount, address bindaddr) public returns (bool) {
    require(!_vaultOnly, "AnyswapV4ERC20: onlyAuth");
    require(bindaddr != address(0), "AnyswapV3ERC20: address(0x0)");
    _burn(msg.sender, amount);
    emit LogSwapout(msg.sender, bindaddr, amount);
    return true;
    }
    
  1. MPC地址:0x63a3d28bb9187809553dd16981c73f498b6b2687, 在BSC发起转账交易: https://bscscan.com/tx/0x12215f7c44dcacc19e4a6943d03b770dc34db6ea1ce85f9716b0c6b4079507d2

多链路由

多链路由支持原生对原生token的跨链,采用流动性池模型。

源链交易

用户发起的交易,调用anySwaputUnderlying方法:

https://bscscan.com/tx/0x7ffb4944ca3d258966b44c541d798946afe64e833ba7cfbabcc9f18d80ac09af

    // Swaps `amount` `token` from this chain to `toChainID` chain with recipient `to` by minting with `underlying`
    function anySwapOutUnderlying(address token, address to, uint amount, uint toChainID) external {
        TransferHelper.safeTransferFrom(AnyswapV1ERC20(token).underlying(), msg.sender, token, amount);
        AnyswapV1ERC20(token).depositVault(amount, msg.sender);
        _anySwapOut(msg.sender, token, to, amount, toChainID);
    }

源码:https://gist.github.com/zhaojun-sh/0df8429d52ae7d71b6d1ff5e8f0050dc#file-anyswaprouterv4-sol-L239

目标链交易

MPC地址发起的交易,调用anySwapInAuto 方法:

https://polygonscan.com/tx/0x0f4aafaa869703525cad458f1c85f850a5ee7d9876a9c208a395bf9df33e2012

   // swaps `amount` `token` in `fromChainID` to `to` on this chainID with `to` receiving `underlying` if possible
    function anySwapInAuto(bytes32 txs, address token, address to, uint amount, uint fromChainID) external onlyMPC {
        _anySwapIn(txs, token, to, amount, fromChainID);
        AnyswapV1ERC20 _anyToken = AnyswapV1ERC20(token);
        address _underlying = _anyToken.underlying();
        if (_underlying != address(0) && IERC20(_underlying).balanceOf(token) >= amount) {
            _anyToken.withdrawVault(to, amount, to);
        }
    }

源码:https://gist.github.com/zhaojun-sh/0df8429d52ae7d71b6d1ff5e8f0050dc#file-anyswaprouterv4-sol-L304

若目标链流动性不足,则采用铸造方式。

更新MPC地址

https://bscscan.com/address/0x2a038e100f8b85df21e4d44121bdbfe0c288a869

https://polygonscan.com/address/0x2a038e100f8b85df21e4d44121bdbfe0c288a869

https://ftmscan.com/address/0x2a038e100f8b85df21e4d44121bdbfe0c288a869

    function changeMPC(address newMPC) public onlyMPC returns (bool) {
        require(newMPC != address(0), "AnyswapV3Router: address(0x0)");
        _oldMPC = mpc();
        _newMPC = newMPC;
        _newMPCEffectiveTime = block.timestamp + 2*24*3600;
        emit LogChangeMPC(_oldMPC, _newMPC, _newMPCEffectiveTime, cID());
        return true;
    }

安全攻击

   2021年7月12日,Anyswap遭黑客攻击,损失超过787万美元。被攻击原因为两个交易采用相同的R值签名,黑客从而可以反推出MPC账户私钥。

    对于签名: 选择随机数$k$,  计算$R = kG, r= R_x, s= k^{-1}(z+ rd_A)$, 对于两个采用相同随机数的签名:$(r, s)$ 和 $(r, s')$,  对应的签名消息分别为:$z$ 和 $z'$,   可以得到:

s-s'=k^{-1}(z-z') \\ k=\frac{z-z'}{s-s'} \\ d_A=\frac{s\cdot k-z}{r}
d_A 即为可以计算出的私钥。

漏洞修复:历史的签名会保存在数据库中,新生成的签名随机数判断是否重复。

            https://github.com/anyswap/CrossChain-Router/commit/ac88cc6861d98645fcdc19d8b985689dd54f5c22  

流程分析

ScanJob

CrossChain-Bridge 启动时会开始扫描任务:

// StartScanJob scan job
func StartScanJob(isServer bool) {
    srcChainCfg := tokens.SrcBridge.GetChainConfig()
    if srcChainCfg.EnableScan && btc.BridgeInstance != nil {
        go btc.BridgeInstance.StartChainTransactionScanJob()
        if srcChainCfg.EnableScanPool {
            go btc.BridgeInstance.StartPoolTransactionScanJob()
        }
        go btc.BridgeInstance.StartSwapHistoryScanJob()
    }
}

连接BTC运行的节点,

// StartChainTransactionScanJob scan job
func (b *Bridge) StartChainTransactionScanJob() {
    go b.StartPoolTransactionScanJob()      // 扫描mempool 的交易

    chainName := b.ChainConfig.BlockChain
    log.Infof("[scanchain] start %v scan chain job", chainName)

    start, latest := b.getStartAndLatestHeight()    //确定起始扫描到和最新的块
    _ = tools.UpdateLatestScanInfo(b.IsSrc, start)
    log.Infof("[scanchain] start %v scan chain loop from %v latest=%v", chainName, start, latest)

    chainCfg := b.GetChainConfig()
    confirmations := *chainCfg.Confirmations

    stable := start
    errorSubject := fmt.Sprintf("[scanchain] get %v block failed", chainName)
    scanSubject := fmt.Sprintf("[scanchain] scanned %v block", chainName)
    for {
        latest := tools.LoopGetLatestBlockNumber(b)
        for h := stable + 1; h <= latest; {
            blockHash, err := b.GetBlockHash(h)    // 从节点获取区块
            if err != nil {
                log.Error(errorSubject, "height", h, "err", err)
                time.Sleep(retryIntervalInScanJob)
                continue
            }
            if scannedBlocks.IsBlockScanned(blockHash) {
                h++
                continue
            }
            txids, err := b.GetBlockTxids(blockHash)
            if err != nil {
                log.Error(errorSubject, "height", h, "blockHash", blockHash, "err", err)
                time.Sleep(retryIntervalInScanJob)
                continue
            }
            for _, txid := range txids {
                b.processTransaction(txid)
            }
            scannedBlocks.CacheScannedBlock(blockHash, h)
            log.Info(scanSubject, "blockHash", blockHash, "height", h, "txs", len(txids))
            h++
        }
        if stable+confirmations < latest {
            stable = latest - confirmations    // 对于未固化的块,会重新扫描
            _ = tools.UpdateLatestScanInfo(b.IsSrc, stable)
        }
        time.Sleep(restIntervalInScanJob)
    }
}

对于扫描到交易,解析后存储到数据库中。

func (b *Bridge) processSwapin(txid string) {
    if tools.IsSwapExist(txid, PairID, "", true) {
        return
    }
    swapInfo, err := b.verifySwapinTx(PairID, txid, true)
    tools.RegisterSwapin(txid, []*tokens.TxSwapInfo{swapInfo}, []error{err})  //数据存储
}

存储到mongoDB后的数据为:

// MgoSwap registered swap
type MgoSwap struct {
    Key       string     `bson:"_id"` // txid + pairid + bind
    PairID    string     `bson:"pairid"`
    TxID      string     `bson:"txid"`    // 交易id
    TxTo      string     `bson:"txto"`
    TxType    uint32     `bson:"txtype"`   // SwapinTx
    Bind      string     `bson:"bind"`  //绑定到目标链的地址
    Status    SwapStatus `bson:"status"`
    InitTime  int64      `bson:"inittime"`
    Timestamp int64      `bson:"timestamp"`
    Memo      string     `bson:"memo"`
}

SwapJob

启动SwapJob 任务:

// StartSwapJob swap job
func StartSwapJob() {
    swapinNonces, swapoutNonces := mongodb.LoadAllSwapNonces()
    if tokens.DstNonceSetter != nil {
        tokens.DstNonceSetter.InitNonces(swapinNonces)
    }
    if tokens.SrcNonceSetter != nil {
        tokens.SrcNonceSetter.InitNonces(swapoutNonces)
    }
    for _, pairCfg := range tokens.GetTokenPairsConfig() {
        AddSwapJob(pairCfg)
    }

    mongodb.MgoWaitGroup.Add(2)
    go startSwapinSwapJob()
    go startSwapoutSwapJob()
}

从MongoDB读取MgoSwap进行处理:

func processSwap(swap *mongodb.MgoSwap, isSwapin bool) (err error) {
    pairID := swap.PairID
    txid := swap.TxID
    bind := swap.Bind

    cacheKey := getSwapCacheKey(isSwapin, txid, bind)   // 判断是否已经被处理
    if cachedSwapTasks.Contains(cacheKey) {
        return errAlreadySwapped
    }

    res, err := mongodb.FindSwapResult(isSwapin, txid, pairID, bind)  
    if err != nil {
        return err
    }

    err = preventReswap(res, isSwapin)
    if err != nil {
        return err
    }

    dcrmAddress, err := checkSwapResult(res, isSwapin)
    if err != nil {
        return err
    }

    logWorker("swap", "start process swap", "pairID", pairID, "txid", txid, "bind", bind, "status", swap.Status, "isSwapin", isSwapin, "value", res.Value)

    srcBridge := tokens.GetCrossChainBridge(isSwapin)
  // 重复进行验证
    swapInfo, err := verifySwapTransaction(srcBridge, pairID, txid, bind, tokens.SwapTxType(swap.TxType))
    if err != nil {
        return fmt.Errorf("[doSwap] reverify swap failed, %w", err)
    }
    if swapInfo.Value.String() != res.Value {
        return fmt.Errorf("[doSwap] reverify swap value mismatch, in db %v != %v", res.Value, swapInfo.Value)
    }
    if !strings.EqualFold(swapInfo.Bind, bind) {
        return fmt.Errorf("[doSwap] reverify swap bind address mismatch, in db %v != %v", bind, swapInfo.Bind)
    }

    swapType := getSwapType(isSwapin)
    args := &tokens.BuildTxArgs{
        SwapInfo: tokens.SwapInfo{
            Identifier: params.GetIdentifier(),
            PairID:     pairID,
            SwapID:     txid,
            SwapType:   swapType,
            TxType:     tokens.SwapTxType(swap.TxType),
            Bind:       bind,
            Reswapping: res.Status == mongodb.Reswapping,
        },
        From:        dcrmAddress,
        OriginValue: swapInfo.Value,
    }

    return dispatchSwapTask(args)
}

构建交易,数据结构为:

// BuildTxArgs struct
type BuildTxArgs struct {
    SwapInfo    `json:"swapInfo,omitempty"`          // 包含Bind 地址 
    From        string     `json:"from,omitempty"`    //Dcrm 地址
    To          string     `json:"to,omitempty"`      // 
    Value       *big.Int   `json:"value,omitempty"`    // 金额
    OriginValue *big.Int   `json:"originValue,omitempty"`
    SwapValue   *big.Int   `json:"swapvalue,omitempty"`
    Memo        string     `json:"memo,omitempty"`
    Input       *[]byte    `json:"input,omitempty"`
    Extra       *AllExtras `json:"extra,omitempty"`
}

通过chan 传递 BuildTxArgs继续处理任务:

func processSwapTask(swapChan <-chan *tokens.BuildTxArgs, dcrmAddress string, isSwapin bool) {
    defer utils.TopWaitGroup.Done()
    for {
        select {
        case <-utils.CleanupChan:
            logWorker("doSwap", "stop process swap task", "isSwapin", isSwapin, "dcrmAddress", dcrmAddress)
            return
        case args := <-swapChan:
            if !strings.EqualFold(args.From, dcrmAddress) || args.SwapType != getSwapType(isSwapin) {
                logWorkerWarn("doSwap", "ignore swap task as mismatch reason", "isSwapin", isSwapin, "dcrmAddress", dcrmAddress, "args", args)
                continue
            }
            err := doSwap(args)
            switch {
            case err == nil,
                errors.Is(err, errAlreadySwapped):
            default:
                logWorkerError("doSwap", "process failed", err, "pairID", args.PairID, "txid", args.SwapID, "swapType", args.SwapType.String(), "value", args.OriginValue)
            }
        }
    }
}

开始执行签名过程,若配置文件有DCRM私钥,则直接在本地签名,否则开始进行DCRM MPC 签名:

func doSwap(args *tokens.BuildTxArgs) (err error) {
    pairID := args.PairID
    txid := args.SwapID
    bind := args.Bind
    swapType := args.SwapType

    isSwapin := swapType == tokens.SwapinType
    resBridge := tokens.GetCrossChainBridge(!isSwapin)

    cacheKey := getSwapCacheKey(isSwapin, txid, bind)
    err = checkAndUpdateProcessSwapTaskCache(cacheKey)
    if err != nil {
        return err
    }
    logWorker("doSwap", "add swap cache", "pairID", pairID, "txid", txid, "bind", bind, "isSwapin", isSwapin, "value", args.OriginValue)
    isCachedSwapProcessed := false
    defer func() {
        if !isCachedSwapProcessed {
            logWorkerError("doSwap", "delete swap cache", err, "pairID", pairID, "txid", txid, "bind", bind, "isSwapin", isSwapin, "value", args.OriginValue)
            cachedSwapTasks.Remove(cacheKey)
        }
    }()

    logWorker("doSwap", "start to process", "pairID", pairID, "txid", txid, "bind", bind, "isSwapin", isSwapin, "value", args.OriginValue)

    rawTx, err := resBridge.BuildRawTransaction(args)
    if err != nil {
        logWorkerError("doSwap", "build tx failed", err, "pairID", pairID, "txid", txid, "bind", bind, "isSwapin", isSwapin)
        return err
    }

    swapNonce := args.GetTxNonce()

    var signedTx interface{}
    var signTxHash string
    tokenCfg := resBridge.GetTokenConfig(pairID)
    for i := 1; i <= 3; i++ { // with retry
        if tokenCfg.GetDcrmAddressPrivateKey() != nil {
            signedTx, signTxHash, err = resBridge.SignTransaction(rawTx, pairID)  // 直接本地签名
        } else {
            signedTx, signTxHash, err = resBridge.DcrmSignTransaction(rawTx, args.GetExtraArgs())  // 采用DCRM签名
        }
        if err == nil {
            break
        }
        logWorkerError("doSwap", "sign tx failed", err, "pairID", pairID, "txid", txid, "bind", bind, "isSwapin", isSwapin, "signCount", i)
        restInJob(retrySignInterval)
    }
    if err != nil {
        return err
    }

    // recheck reswap before update db
    res, err := mongodb.FindSwapResult(isSwapin, txid, pairID, bind)
    if err != nil {
        return err
    }
    err = preventReswap(res, isSwapin)
    if err != nil {
        return err
    }

    // update database before sending transaction
    matchTx := &MatchTx{
        SwapTx:    signTxHash,
        SwapType:  swapType,
        SwapNonce: swapNonce,
    }
    if args.SwapValue != nil {
        matchTx.SwapValue = args.SwapValue.String()
    } else {
        matchTx.SwapValue = tokens.CalcSwappedValue(pairID, args.OriginValue, isSwapin).String()
    }
    err = updateSwapResult(txid, pairID, bind, matchTx)
    if err != nil {
        logWorkerError("doSwap", "update swap result failed", err, "pairID", pairID, "txid", txid, "bind", bind, "isSwapin", isSwapin)
        return err
    }
    isCachedSwapProcessed = true

    err = mongodb.UpdateSwapStatus(isSwapin, txid, pairID, bind, mongodb.TxProcessed, now(), "")
    if err != nil {
        logWorkerError("doSwap", "update swap status failed", err, "pairID", pairID, "txid", txid, "bind", bind, "isSwapin", isSwapin)
        return err
    }

    txHash, err := sendSignedTransaction(resBridge, signedTx, args) //广播交易,最终调用:eth_sendRawTransaction
    if err == nil {
        logWorker("doSwap", "send tx success", "pairID", pairID, "txid", txid, "bind", bind, "isSwapin", isSwapin, "swapNonce", swapNonce, "txHash", txHash)
        if txHash != signTxHash {
            logWorkerError("doSwap", "send tx success but with different hash", errSendTxWithDiffHash, "pairID", pairID, "txid", txid, "bind", bind, "isSwapin", isSwapin, "swapNonce", swapNonce, "txHash", txHash, "signTxHash", signTxHash)
            _ = replaceSwapResult(txid, pairID, bind, txHash, matchTx.SwapValue, isSwapin)
        }
    }
    return err
}

进行MPC 签名:

// DoSign dcrm sign msgHash with context msgContext
func DoSign(signPubkey string, msgHash, msgContext []string) (keyID string, rsvs []string, err error) {
    if !params.IsDcrmEnabled() {
        return "", nil, errSignIsDisabled
    }
    log.Debug("dcrm DoSign", "msgHash", msgHash, "msgContext", msgContext)
    if signPubkey == "" {
        return "", nil, errSignWithoutPublickey
    }
    for i := 0; i < retrySignLoop; i++ {
        for _, dcrmNode := range allInitiatorNodes {  // 参与MPC的节点
            if err = pingDcrmNode(dcrmNode); err != nil {
                continue
            }
            signGroupsCount := int64(len(dcrmNode.signGroups))
            // randomly pick first subgroup to sign
            randIndex, _ := rand.Int(rand.Reader, big.NewInt(signGroupsCount))
            startIndex := randIndex.Int64()
            i := startIndex
            for {
                keyID, rsvs, err = doSignImpl(dcrmNode, i, signPubkey, msgHash, msgContext)
                if err == nil {
                    return keyID, rsvs, nil
                }
                i = (i + 1) % signGroupsCount
                if i == startIndex {
                    break
                }
            }
        }
        time.Sleep(2 * time.Second)
    }
    log.Warn("dcrm DoSign failed", "msgHash", msgHash, "msgContext", msgContext, "err", err)
    return "", nil, errDoSignFailed
}

调用MPC 节点进行签名:

// Sign call sign
func Sign(raw, rpcAddr string) (string, error) {
    var result DataResultResp
    err := httpPostTo(&result, rpcAddr, "sign", raw)
    if err != nil {
        return "", wrapPostError("sign", err)
    }
    if result.Status != successStatus {
        return "", newWrongStatusError("sign", result.Status, result.Error)
    }
    return result.Data.Result, nil
}

AcceptSignJob

获取需要进行的签名信息:

func startAcceptProducer() {
    i := 0
    for {
        signInfo, err := dcrm.GetCurNodeSignInfo(maxAcceptSignTimeInterval)   //通过接口调用获取签名
        if err != nil {
            logWorkerError("accept", "getCurNodeSignInfo failed", err)
            time.Sleep(retryInterval)
            continue
        }
        i++
        if i%7 == 0 {
            logWorker("accept", "getCurNodeSignInfo", "count", len(signInfo))
        }
        for _, info := range signInfo {
            if utils.IsCleanuping() {
                return
            }
            if info == nil { // maybe a dcrm RPC problem
                continue
            }
            keyID := info.Key
            if cachedAcceptInfos.Contains(keyID) {
                logWorkerTrace("accept", "ignore cached accept sign info before dispatch", "keyID", keyID)
                continue
            }
            logWorker("accept", "dispatch accept sign info", "keyID", keyID)
            acceptInfoCh <- info // produce           // 
        }
        if utils.IsCleanuping() {
            return
        }
        time.Sleep(waitInterval)
    }
}

处理签名信息, 确定是否进行签名:

func processAcceptInfo(info *dcrm.SignInfoData) {
    defer atomic.AddInt64(&curAcceptRoutines, -1)

    keyID := info.Key
    if !checkAndUpdateCachedAcceptInfoMap(keyID) {
        return
    }
    isProcessed := false
    defer func() {
        if !isProcessed {
            cachedAcceptInfos.Remove(keyID)
        }
    }()

    args, err := verifySignInfo(info) // 进行签名信息验证,会调用链上全节点的信息

    ctx := []interface{}{
        "keyID", keyID,
    }
    if args != nil {
        ctx = append(ctx,
            "identifier", args.Identifier,
            "swaptype", args.SwapType.String(),
            "pairID", args.PairID,
            "swapID", args.SwapID,
            "bind", args.Bind,
        )
    }

    switch {
    case errors.Is(err, tokens.ErrTxNotStable),
        errors.Is(err, tokens.ErrTxNotFound),
        errors.Is(err, tokens.ErrRPCQueryError):
        ctx = append(ctx, "err", err)
        logWorkerTrace("accept", "ignore sign", ctx...)
        return
    case errors.Is(err, errIdentifierMismatch):
        ctx = append(ctx, "err", err)
        logWorkerTrace("accept", "discard sign", ctx...)
        isProcessed = true
        return
    case errors.Is(err, errInitiatorMismatch),
        errors.Is(err, errWrongMsgContext),
        errors.Is(err, tokens.ErrUnknownPairID),
        errors.Is(err, tokens.ErrNoBtcBridge):
        ctx = append(ctx, "err", err)
        logWorker("accept", "discard sign", ctx...)
        isProcessed = true
        return
    }

    agreeResult := acceptAgree          
    if err != nil {
        logWorkerError("accept", "DISAGREE sign", err, ctx...)
        agreeResult = acceptDisagree
    }
    ctx = append(ctx, "result", agreeResult) 

    res, err := dcrm.DoAcceptSign(keyID, agreeResult, info.MsgHash, info.MsgContext)  // 接受签名
    if err != nil {
        ctx = append(ctx, "rpcResult", res)
        logWorkerError("accept", "accept sign job failed", err, ctx...)
    } else {
        logWorker("accept", "accept sign job finish", ctx...)
        isProcessed = true
    }
}

接受签名后的处理:


// DoAcceptSign accept sign
func DoAcceptSign(keyID, agreeResult string, msgHash, msgContext []string) (string, error) {
    nonce := uint64(0)
    data := AcceptData{
        TxType:  "ACCEPTSIGN",
        Key:     keyID,
        Accept:  agreeResult,
        MsgHash: msgHash,
        //MsgContext: msgContext, // context is verified on top level
        TimeStamp: common.NowMilliStr(),
    }
    payload, err := json.Marshal(data)
    if err != nil {
        return "", err
    }
    rawTX, err := BuildDcrmRawTx(nonce, payload, defaultDcrmNode.keyWrapper)
    if err != nil {
        return "", err
    }
    return AcceptSign(rawTX)
}

调用acceptSign接口, 执行MPC签名.

// AcceptSign call acceptSign
func AcceptSign(raw string) (string, error) {
    var result DataResultResp
    err := httpPost(&result, "acceptSign", raw)
    if err != nil {
        return "", wrapPostError("acceptSign", err)
    }
    if result.Status != successStatus {
        return "", newWrongStatusError("acceptSign", result.Status, result.Error)
    }
    return result.Data.Result, nil
}

参考

https://github.com/anyswap/Anyswap-Audit/blob/master/whitepaper/Anyswap-whitepaper.pdf

https://anyswap.exchange/#/router

https://github.com/anyswap/CrossChain-Bridge

https://github.com/anyswap/mBTC

https://fusiondev.gitbook.io/fusion/learn/what-is-the-vision-of-fusion/how-does-fusion-and-anyswap-compare

https://anyswap-faq.readthedocs.io/en/latest/

https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm

https://anyswap.medium.com/anyswap-multichain-router-v3-exploit-statement-6833f1b7e6fb

相关文章

  • Anyswap

    门限签名 通过(t, n)门限签名,即可将一个秘密(私钥)分成n份,任意 t+1个成员可以通过多轮交互,在不重构私...

  • 20211009坚持自己的见解

    20211009 坚持自己的见解 最近跨链桥生意又起来了,作为跨链技术中的佼佼者anyswap,借助fusion链...

网友评论

      本文标题:Anyswap

      本文链接:https://www.haomeiwen.com/subject/adtzfrtx.html