Slide 1

Slide 1 text

Dapper Contract 解説 2019/08/26 @avcdsld

Slide 2

Slide 2 text

基礎編

Slide 3

Slide 3 text

Ethereum のアドレスは 2 種類ある ● EOA (Externally Owned Account) 秘密鍵で署名して Tx を送れる ● コントラクトアカウント (Contract account) 秘密鍵がなく署名できない EOA 起点で、インターナル Tx を送れる

Slide 4

Slide 4 text

コントラクトウォレットとは ● コントラクトアカウントを ユーザーのウォレットとして使っているもの ● 現在リリースされているプロダクト: ○ Dapper https://www.meetdapper.com/ ○ Argent https://www.argent.xyz/

Slide 5

Slide 5 text

Dapper

Slide 6

Slide 6 text

Argent

Slide 7

Slide 7 text

Dapper 概説

Slide 8

Slide 8 text

Dapper の概要 ● CryptoKitties 開発チームが開発 ● MetaMask ライクなブラウザ拡張 ● Gas 代を Dapper チーム側で持つことができる ● リカバリー機能がある ● オープンソース https://github.com/dapperlabs/dapper-contracts

Slide 9

Slide 9 text

要点 ● 2 of 2 のマルチシグウォレットになっている ○ Device Key ユーザーがデバイスに秘密鍵を保管 ○ Cosigning Key Dapper チームがサーバーに秘密鍵を保管 ● リカバリー用の Key がある ○ Recovery Key Dapperチームがコールドウォレットに保管 ○ Backup Key リカバリー時に、唯一の Device Key になる ユーザーが任意の場所に秘密鍵を保管 (たぶん) ※ コードでは authorizedAddress / signer ※ コードでは cosigner

Slide 10

Slide 10 text

構図 ユーザー Dapperチーム Contract Tx送信 コールド ウォレット Recovery Key Device Key Cosigning Key transfer などの 操作を実行 署名

Slide 11

Slide 11 text

構図 ユーザー Dapperチーム Contract Tx送信 Device Key Cosigning Key ・既存の Key を無効化 ・新しい Key を登録 リカバリー時 コールド ウォレット Recovery Key

Slide 12

Slide 12 text

コントラクトの呼び出しフローと いくつかの例

Slide 13

Slide 13 text

コントラクトの呼び出しフロー 1. コントラクト生成 ― コントラクトからコントラクトを生成 2. 初期化 ― Device Key, Cosigning Key, Recovery Key を設定 3. 利用 ― 送金などの操作内容の Tx をコントラクトに送信 ○ invoke0(): Device Key = Cosigning Key の Tx 送信 ○ invoke1SignerSend(): Cosigning Key の署名 + Device Key の Tx 送信 ○ invoke1CosignerSend(): Device Key の署名 + Cosigning Key の Tx 送信 ○ invoke2(): Device Key の署名 + Cosigning Key の署名 + 任意者による Tx 送信 4. リカバリー ― 既存の Key を無効化して、Backup Key を唯一の Device Key   として登録する Tx を Recovery Key で送信

Slide 14

Slide 14 text

ETH の受け取り fallback関数 ・Received イベント発行 Dapper Contract ユーザー 3 ETH

Slide 15

Slide 15 text

ETH の送金 ユーザー Dapper invoke1CosignerSend() ・署名/Nonce/送信者のチェック Dapper Contract internalInvoke() ・送金先アドレスに、コントラクト内の  ETH を送金 送金先ユーザー ● ユーザーの署名 ● Nonce ● Data ○ 送信先アドレス ○ 送金額 (3 ETH) ○ InnerData (空) 3 ETH * invoke1CosignerSend でなく, invoke1SignerSend, invoke2, invoke0(Device Key = Cosiging Key の場合)を使うこともできる

Slide 16

Slide 16 text

ERC-721 トークンの受け取り transferFrom() ・トークンの所有者変更 ERC-721 Token Contract ユーザー ● 送信先のコントラクトウォレットのアドレス ● TokenId

Slide 17

Slide 17 text

ERC-721 トークンの受け取り safeTransferFrom() ・onERC721Received チェック ・トークンの所有者変更 ERC-721 Token Contract ユーザー ● 送信先のコントラクトウォレットのアドレス ● TokenId onERC721Received() Dapper Contract safeTransferFrom を使う場合

Slide 18

Slide 18 text

ERC-721 トークンの送信 ユーザー Dapper invoke1CosignerSend() ・署名/Nonce/送信者のチェック Dapper Contract internalInvoke() ・ERC-721 トークンのコントラクト  アドレスの指定された関数を  InnerDate で呼び出す ● ユーザーの署名 ● Nonce ● Data ○ ERC-721 トークンの コントラクトアドレス ○ 送金額 (0 ETH) ○ InnerData ■ 関数シグネチャ ■ 送信先アドレス ■ TokenId * invoke1CosignerSend でなく, invoke1SignerSend, invoke2, invoke0(Device Key = Cosiging Key の場合)を使うこともできる transferFrom() ・トークンの所有者変更 ERC-721 Token Contract

Slide 19

Slide 19 text

Dapper コントラクトのここがすごい

Slide 20

Slide 20 text

Dapperコントラクトのここがすごい 技術面 ● コントラクト生成が効率的 ● マルチシグ/メタトランザクションの実践的な実装 ユーザービリティ面 ● サービス提供者が Gas 代を肩代わりできる ● 盗難リスクを抑えたリカバリー機能の提供 ● コントラクトにユーザーが機能を追加できる

Slide 21

Slide 21 text

コントラクト生成が効率的 ● EIP-1167: Minimal Proxy Contract を利用 ● 生成するコントラクトは たった 45 バイト(Gas ~100K) ● 指定したアドレスのコントラクトに DelegateCall ※フルコードを生成する関数も用意はされている

Slide 22

Slide 22 text

コントラクト生成が効率的 EIP 1167 のバイトコード( の箇所が任意のコントラクトアドレス) Etherscan でのデコンパイル結果

Slide 23

Slide 23 text

コントラクト生成が効率的 Etherscan でみると、Minimal Proxy Contract として表示されている (少し前までこの表示はなかった. 利用例が増えてきたのかも)

Slide 24

Slide 24 text

マルチシグ/メタトランザクションの実践的な実装 ● Ethereum は標準でマルチシグ機能を持っていない ● セキュアなマルチシグ/メタトランザクションを書くのは難しい ● コード監査されていて、ユーザーも多いので 実装例として非常に価値がある

Slide 25

Slide 25 text

サービス提供者が Gas 代を肩代わりできる ユーザー サービス提供者 Contract Tx送信 サービス提供者が Gas 代を負担 署名 ● ユーザーが ETH を持っていなくてもサービスを利用できる ● ETH を使わないサービスは特に UX 向上が見込める

Slide 26

Slide 26 text

サービス提供者が Gas 代を肩代わりできる ユーザー サービス提供者 Contract Tx送信 署名 ● ちなみに、 ユーザーに Gas 代を払わせることもできる

Slide 27

Slide 27 text

盗難リスクを抑えたリカバリー機能の提供 通常のウォレットでは… ● ユーザーが秘密鍵をなくしたらリカバリー不可能 ● …かといって、サービス提供側が秘密鍵を持つと、 ハッキングによる盗難リスクが高まる Dapper コントラクトウォレットでは… ● ウォレットがコントラクトなので、秘密鍵はなく、 独自のリカバリー機能を提供できている ● サービス提供者の内部犯行がない前提なら、 Recovery Key をコールドウォレットで管理できるため 盗難リスクを抑えたリカバリー機能を実現できる

Slide 28

Slide 28 text

盗難リスクを抑えたリカバリー機能の提供 /// @notice Performs an emergency recovery operation, removing all existing authorizations and setting /// a sole new authorized address with optional cosigner. THIS IS A SCORCHED EARTH SOLUTION, and great /// care should be taken to ensure that this method is never called unless it is a last resort. See the /// comments above about the proper kinds of addresses to use as the recoveryAddress to ensure this method /// is not trivially abused. /// @param _authorizedAddress the new and sole authorized address /// @param _cosigner the corresponding cosigner address, can be equal to _authorizedAddress function emergencyRecovery(address _authorizedAddress, uint256 _cosigner) external onlyRecoveryAddress { require(_authorizedAddress != address(0), "Authorized addresses must not be zero."); require(_authorizedAddress != recoveryAddress, "Do not use the recovery address as an authorized address."); require(address(_cosigner) != address(0), "The cosigner must not be zero."); // Incrementing the authVersion number effectively erases the authorizations mapping. See the comments // on the authorizations variable (above) for more information. authVersion += AUTH_VERSION_INCREMENTOR; // Store the new signer/cosigner pair as the only remaining authorized address authorizations[authVersion + uint256(_authorizedAddress)] = _cosigner; emit EmergencyRecovery(_authorizedAddress, _cosigner); } リカバリー用の関数 https://github.com/dapperlabs/dapper-contracts/blob/0321efcc80c413dc1d3a3cd30ae02929b3ddadfe/contracts/Wallet/CoreWallet.sol#L299

Slide 29

Slide 29 text

コントラクトにユーザーが機能を追加できる ● 任意の関数とコントラクトアドレスを登録できる ● 例えば 新しいトークン規格が出てきても、同じコントラクトで 対応できる(サポートしているインターフェースを返せる) https://github.com/dapperlabs/dapper-contracts/blob/0321efcc80c413dc1d3a3cd30ae02929b3ddadfe/contracts/Wallet/CoreWallet.sol#L261 function setDelegate(bytes4 _interfaceId, address _delegate) external onlyInvoked { delegates[_interfaceId] = _delegate; emit DelegateUpdated(_interfaceId, _delegate); } 登録用の関数 ※ EIP-165 準拠

Slide 30

Slide 30 text

コントラクトにユーザーが機能を追加できる function() external payable { : if (msg.data.length > 0) { address delegate = delegates[msg.sig]; : assembly { calldatacopy(0, 0, calldatasize()) let result := staticcall(gas, delegate, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) : } } } https://github.com/dapperlabs/dapper-contracts/blob/0321efcc80c413dc1d3a3cd30ae02929b3ddadfe/contracts/Wallet/CoreWallet.sol#L221 ※ fallback 関数(無名の関数)は、コントラクトの関数呼び出しで関数シグネチャが見つからない場合に呼ばれる ● fallback 関数に、追加した機能を呼び出すしくみが入っている

Slide 31

Slide 31 text

コントラクトにユーザーが機能を追加できる ● 追加した機能をサポートインターフェースとして返すしくみ https://github.com/dapperlabs/dapper-contracts/blob/0321efcc80c413dc1d3a3cd30ae02929b3ddadfe/contracts/Wallet/CoreWallet.sol#L425 function supportsInterface(bytes4 interfaceID) external view returns (bool) { // First check if the ID matches one of the interfaces we support statically. if ( interfaceID == this.supportsInterface.selector || // ERC165 interfaceID == ERC721_RECEIVED_FINAL || // ERC721 Final interfaceID == ERC721_RECEIVED_DRAFT || // ERC721 Draft interfaceID == ERC223_ID || // ERC223 interfaceID == ERC1271_VALIDSIGNATURE // ERC1271 ) { return true; } // If we don't support the interface statically, check whether we have added // dynamic support for it. return uint256(delegates[interfaceID]) > 0; }

Slide 32

Slide 32 text

その他 開発関連・疑問点

Slide 33

Slide 33 text

Dapper ブラウザ拡張とのインテグレーション ● 要件:Node v9.x.x, web3.js v1.x.x ● Dapper Sandbox https://www.meetdapper.com/docs/start ○ Rinkeby で動作する開発者向けのブラウザ拡張. Gas 代を Dapper チームが払ってくれる ● 通常の Web3 プロバイダーと同じように Dapper に接続できる ● EIP-1102 (eth_requestAccounts) にも対応 // Transactions will be signed and sent via your Dapper account. const web3 = new Web3(window.ethereum); const Contract = new web3.eth.Contract(contractJSON.abi, <コントラクトアドレス>); const accounts = await window.ethereum.enable(); // Use Infura provider to listen for events from your contract! const newestWeb3 = new Web3(new Web3.providers.WebsocketProvider( "wss://rinkeby.infura.io/ws/v3/")); newestWeb3.eth.defaultAccount = accounts[0]; const eventSource = new newestWeb3.eth.Contract(contractJSON.abi, <コントラクトアドレス>); return { web3, accounts, Contract, eventSource }

Slide 34

Slide 34 text

疑問点 ● cosigner が address 型でなく uint256 型なのはなぜ? → assembly でデータを処理するとき、mload だと 32 byte でしか読めな いので、12 byte 分 左シフトしている. そのため、12 byte のダミーデータ をつけられるように uint256 型にしている ● Dapper ブラウザ拡張と MetaMask との違いはあるか? → 開発者向けドキュメントによると、同じようにインテグレーションできる ● 関数シグネチャが同じで、異なるコントラクトの呼び出しを使い分けること はできない?→ できないはず ● コントラクトにユーザーが機能を追加できるが、例えばどんなユースケース があるか? → (いいユースケースがあれば知りたい)

Slide 35

Slide 35 text

参考 https://github.com/dapperlabs/dapper-contracts https://etherscan.io/address/0x37932f3eca864632156ccba7e2814b51a374caec#code https://medium.com/dapperlabs/why-dapper-is-a-smart-contract-wallet-ef44cc51cfa5 https://medium.com/dapperlabs/introducing-dappers-multi-device-support-db6b4f53fb https://blog.sigmaprime.io/dapper-wallet-review.html