<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      solidity學習之多簽錢包

      什么是多簽錢包

      多簽錢包是一種特殊的錢包,可以添加多個簽名用戶,在執行交易的時候需要多個持有者同時簽名才能提交,比如3個用戶的多簽錢包需要2個以上的用戶同時簽名。

      這種設計可以有效防止單點故障,保證資產的安全,在dao群中有廣泛的應用。

      實現邏輯

      多簽錢包其實是一個智能合約,在合約中存儲了多簽持有者的信息。

      執行交易時,需要先將交易組裝成data,計算出hash。拿到交易hash后,多簽用戶需要分別對hash進行簽名,并且將得到的簽名拼接成最終的簽名作為參數。

      將交易data和signature作為參數調用合約中的方法,合約做的工作是:

      1. 判斷簽名數量需要大于多簽規定的簽名數量
      2. 拆分簽名
      3. 對應每條簽名通過ecrecover還原出簽名地址,判斷該地址是否存儲于合約中
      4. 滿足簽名條件后,根據data執行交易

      具體實現

      ecrecover

      ecrecover是solidity內置的驗簽方法,定義如下:

      function ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) public pure returns (address)
      

      其中hash為交易哈希,也就是簽名的msg,v,r,s是從簽名中拆分得到的,返回值為簽名的公鑰。

      簽名拆分

      這里使用的是ECDSA標準的簽名,

      一個標準的 ECDSA 簽名是:

      • bytes32 r
      • bytes32 s
      • uint8 v(27 或 28,也可能是 0 或 1)

      總共65字節的內容,因此要寫一個對signature進行拆分的方法,得到v、r、s

      function signatureSplit(bytes memory signatures, uint256 pos)
          internal
          pure
          returns (
              uint8 v,
              bytes32 r,
              bytes32 s
          )
      {
          // 簽名的格式:{bytes32 r}{bytes32 s}{uint8 v}
          assembly {
              let signaturePos := mul(0x41, pos)
              r := mload(add(signatures, add(signaturePos, 0x20)))
              s := mload(add(signatures, add(signaturePos, 0x40)))
              v := and(mload(add(signatures, add(signaturePos, 0x41))), 0xff)
          }
      }
      

      這里使用了內聯匯編的寫法,因為涉及到了內存的讀取。

      signatures是拼接后的簽名,pos代表的是當前讀取的簽名在signatures中是第幾個,即索引值。

      let signaturePos := mul(0x41, pos)是用來定位當前拆分出簽名的偏移量,因為每個簽名的長度是65字節,換算成16進制就是0x41,所以偏移量就是0x41*pos

      r := mload(add(signatures, add(signaturePos, 0x20)))中,mload的作用是從某個內存地址開始,向后讀取固定32字節的內容,而add(signatures, add(signaturePos, 0x20))從內部到外部的兩個add分別表示:

      • add(signaturePos, 0x20)表示signaturePos0x20偏移量相加,因為bytes類型的前32字節是頭部,而非實際數據,所以讀取時先偏移到signaturePos,即簽名起點,再向后偏移32個字節。
      • add(signatures, pos)指的是從拼接簽名的起始位置,偏移到pos的位置

      這兩個add,一個是偏移量的相加,一個是位置的移動,但在內聯匯編計算中都是使用add方法,因為signatures是一個指針,指向的地址也是用偏移字節數表示的,代表從內存0的位置偏移的數量。

      用同樣的原理可以取出s和v,要注意的是v的字節數為1,而mload固定取32字節,所以使用and()方法與0xff做了一個與操作,得到最低位1字節的值。

      設計好驗簽邏輯之后,就可以開始寫合約內容了。

      	address[] public owners;                   // 多簽持有人數組 
        mapping(address => bool) public isOwner;   // 記錄一個地址是否為多簽持有人
        uint256 public ownerCount;                 // 多簽持有人數量
        uint256 public threshold;                  // 多簽執行門檻,交易至少有n個多簽人簽名才能被執行。
        uint256 public nonce;                      // nonce,防止簽名重放攻擊
      
      constructor(        
          address[] memory _owners,
          uint256 _threshold
      ) {
          _setupOwners(_owners, _threshold);
      }
      
      /// @param _owners: 多簽持有人數組
      /// @param _threshold: 多簽執行門檻,至少有幾個多簽人簽署了交易
      function _setupOwners(address[] memory _owners, uint256 _threshold) internal {
          // 多簽執行門檻 小于或等于 多簽人數
          require(_threshold <= _owners.length, "invalid threshold");
          // 多簽執行門檻至少為1
          require(_threshold >= 1, "threshold at least 1");
      
          for (uint256 i = 0; i < _owners.length; i++) {
              address owner = _owners[i];
              // 多簽人不能為0地址,本合約地址,不能重復
              require(owner != address(0) && owner != address(this) && !isOwner[owner], "owner can not be repeated");
              owners.push(owner);
              isOwner[owner] = true;
          }
          ownerCount = _owners.length;
          threshold = _threshold;
      }
      

      此處是簡化多簽錢包的邏輯,不做多簽持有人的變更,持有人的列表和threshold都是固定不變的。

      然后寫一個打包交易hash的方法,在參數中增加了一個chainid,這是為了防止拿到簽名之后可以去其他鏈執行,進行交易重放。

      /// @dev 編碼交易數據
      /// @param to 目標合約地址
      /// @param value msg.value,支付的以太坊
      /// @param data calldata
      /// @param _nonce 交易的nonce.
      /// @param chainid 鏈id
      /// @return 交易哈希bytes.
      function encodeTransactionData(
          address to,
          uint256 value,
          bytes memory data,
          uint256 _nonce,
          uint256 chainid
      ) public pure returns (bytes32) {
          bytes32 safeTxHash =
              keccak256(
                  abi.encode(
                      to,
                      value,
                      keccak256(data),
                      _nonce,
                      chainid
                  )
              );
          return safeTxHash;
      }
      

      然后寫一個驗簽方法:

      /**
       * @dev 檢查簽名和交易數據是否對應。如果是無效簽名,交易會revert
       * @param dataHash 交易數據哈希
       * @param signatures 幾個多簽簽名打包在一起
       */
      function checkSignatures(
          bytes32 dataHash,
          bytes memory signatures
      ) public view {
          // 讀取多簽執行門檻
          uint256 _threshold = threshold;
          require(_threshold > 0, "threshold not set");
      
          // 檢查簽名長度足夠長
          require(signatures.length >= _threshold * 65, "signature not satisify threshold");
      
          // 通過一個循環,檢查收集的簽名是否有效
          // 大概思路:
          // 1. 用ecdsa先驗證簽名是否有效
          // 2. 利用 currentOwner > lastOwner 確定簽名來自不同多簽(多簽地址遞增)
          // 3. 利用 isOwner[currentOwner] 確定簽名者為多簽持有人
          address lastOwner = address(0); 
          address currentOwner;
          uint8 v;
          bytes32 r;
          bytes32 s;
          uint256 i;
          for (i = 0; i < _threshold; i++) {
              (v, r, s) = signatureSplit(signatures, i);
              // 利用ecrecover檢查簽名是否有效
              currentOwner = ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", dataHash)), v, r, s);
              require(currentOwner > lastOwner && isOwner[currentOwner], "singer not owner");
              lastOwner = currentOwner;
          }
      }
      

      幾個要注意的地方:

      1. threshold是否滿足的判斷是由signatures的長度來判斷的,因為單個簽名的長度固定是65字節。
      2. ecrecover的時候拼接了\x19Ethereum Signed Message:\n32,這是因為在使用eth_sign或者錢包簽名的時候,會自動在前面加上這一段話,用于標識是簽名而非真實的交易數據,所以驗簽的時候也需要加上。
      3. lastOwner的記錄是因為signatures的拼接是根據address從小到大進行拼的,這樣保證了多簽拼接順序的固定,所以驗簽時還需要判斷address之間的大小關系。

      最后實現一個執行合約的方法:

      /// @dev 在收集足夠的多簽簽名后,執行交易
      /// @param to 目標合約地址
      /// @param value msg.value,支付的以太坊
      /// @param data calldata
      /// @param signatures 打包的簽名,對應的多簽地址由小到達,方便檢查。 ({bytes32 r}{bytes32 s}{uint8 v}) (第一個多簽的簽名, 第二個多簽的簽名 ... )
      function execTransaction(
          address to,
          uint256 value,
          bytes memory data,
          bytes memory signatures
      ) public payable virtual returns (bool success) {
          // 編碼交易數據,計算哈希
          bytes32 txHash = encodeTransactionData(to, value, data, nonce, block.chainid);
          nonce++;  // 增加nonce
          checkSignatures(txHash, signatures); // 檢查簽名
          // 利用call執行交易,并獲取交易結果
          (success, ) = to.call{value: value}(data);
          if (success) emit ExecutionSuccess(txHash);
          else emit ExecutionFailure(txHash);
      }
      
      posted @ 2025-08-08 23:13  Felix07  閱讀(47)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 99久9在线视频 | 传媒| 精品无码国产污污污免费| 麻豆一区二区三区精品视频| 国产男女猛烈无遮挡免费视频网站| 国产成人精品一区二三区| 97欧美精品系列一区二区| 国产高清一区二区不卡| 国产精品国产高清国产av| 免费看欧美全黄成人片| 国产精品一区二区在线蜜芽tv| 国产成人无码性教育视频| 蜜臀av一区二区三区日韩| 亚洲午夜成人精品电影在线观看| 国产精品三级中文字幕| 国产精品成人网址在线观看| 东京热人妻无码一区二区av| 亚洲区综合区小说区激情区| 制服丝袜中文字幕在线| 日韩一区二区三区精品区| 午夜福利精品国产二区| 亚洲日本VA中文字幕在线| 国产蜜臀久久av一区二区| 国产精品亚洲综合色区丝瓜| 国产亚洲一区二区三不卡| 国产精品中文一区二区| 福利一区二区1000| 久久人人97超碰精品| 亚洲AV永久无码嘿嘿嘿嘿| 国产成人精品2021欧美日韩| 阿勒泰市| 精品国产成人国产在线观看| 中文字幕 日韩 人妻 无码| 亚洲偷自拍国综合| 久久精品免视看国产成人| 一区二区视频| 正在播放肥臀熟妇在线视频| 久热伊人精品国产中文| 偷拍久久大胆的黄片视频| 亚洲一卡2卡三卡四卡精品| 不卡一区二区三区视频播放 | 久久99精品久久久久麻豆|