Dapper Contract 解説
2019/08/26 @avcdsld
基礎編
Ethereum のアドレスは 2 種類ある
EOA (Externally Owned Account) 秘密鍵で署名して Tx を送れる
コントラクトアカウント (Contract account) 秘密鍵がなく署名できない EOA 起点で、インターナル Tx を送れる
コントラクトウォレットとは
コントラクトアカウントをユーザーのウォレットとして使っているもの
現在リリースされているプロダクト:
Dapper https://www.meetdapper.com/
Argent https://www.argent.xyz/
Dapper
Argent
Dapper 概説
Dapper の概要
CryptoKitties 開発チームが開発
MetaMask ライクなブラウザ拡張
Gas 代を Dapper チーム側で持つことができる
リカバリー機能がある
オープンソース https://github.com/dapperlabs/dapper-contracts
要点
2 of 2 のマルチシグウォレットになっている
Device Key ユーザーがデバイスに秘密鍵を保管
Cosigning Key Dapper チームがサーバーに秘密鍵を保管
リカバリー用の Key がある
Recovery Key Dapperチームがコールドウォレットに保管
Backup Key リカバリー時に、唯一の Device Key になる ユーザーが任意の場所に秘密鍵を保管
(たぶん)
※ コードでは authorizedAddress / signer
※ コードでは cosigner
構図
ユーザー
Dapperチーム
Contract
Tx送信
コールド
ウォレット
Recovery Key
Device
Key
Cosigning
Key
transfer などの操作を実行
署名
構図
ユーザー
Dapperチーム
Contract
Tx送信
Device
Key
Cosigning
Key
・既存の Key を無効化・新しい Key を登録
リカバリー時
コールド
ウォレット
Recovery Key
コントラクトの呼び出しフローと
いくつかの例
コントラクトの呼び出しフロー
コントラクト生成 ― コントラクトからコントラクトを生成
初期化 ― Device Key, Cosigning Key, Recovery Key を設定
利用 ― 送金などの操作内容の 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 送信
リカバリー ― 既存の Key を無効化して、Backup Key を唯一の Device Key として登録する Tx を Recovery Key で送信
ETH の受け取り
fallback関数
・Received イベント発行
Dapper Contract
ユーザー
3 ETH
ETH の送金
ユーザー
Dapper
invoke1CosignerSend()
・署名/Nonce/送信者のチェック
Dapper Contract
internalInvoke()
・送金先アドレスに、コントラクト内の ETH を送金
送金先ユーザー
ユーザーの署名
Nonce
Data
送信先アドレス
送金額 (3 ETH)
InnerData (空)
3 ETH
* invoke1CosignerSend でなく, invoke1SignerSend, invoke2, invoke0(Device Key = Cosiging Key の場合)を使うこともできる
ERC-721 トークンの受け取り
transferFrom()
・トークンの所有者変更
ERC-721 Token Contract
ユーザー
送信先のコントラクトウォレットのアドレス
TokenId
ERC-721 トークンの受け取り
safeTransferFrom()
・onERC721Received チェック
・トークンの所有者変更
ERC-721 Token Contract
ユーザー
送信先のコントラクトウォレットのアドレス
TokenId
onERC721Received()
Dapper Contract
safeTransferFrom を使う場合
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
Dapper コントラクトのここがすごい
Dapperコントラクトのここがすごい
技術面
コントラクト生成が効率的
マルチシグ/メタトランザクションの実践的な実装
ユーザービリティ面
サービス提供者が Gas 代を肩代わりできる
盗難リスクを抑えたリカバリー機能の提供
コントラクトにユーザーが機能を追加できる
コントラクト生成が効率的
EIP-1167: Minimal Proxy Contract を利用
生成するコントラクトは たった 45 バイト(Gas ~100K)
指定したアドレスのコントラクトに DelegateCall
※フルコードを生成する関数も用意はされている
コントラクト生成が効率的
EIP 1167 のバイトコード( bebebe... の箇所が任意のコントラクトアドレス)
Etherscan でのデコンパイル結果
コントラクト生成が効率的
Etherscan でみると、Minimal Proxy Contract として表示されている(少し前までこの表示はなかった. 利用例が増えてきたのかも)
マルチシグ/メタトランザクションの実践的な実装
Ethereum は標準でマルチシグ機能を持っていない
セキュアなマルチシグ/メタトランザクションを書くのは難しい
コード監査されていて、ユーザーも多いので実装例として非常に価値がある
サービス提供者が Gas 代を肩代わりできる
ユーザー
サービス提供者
Contract
Tx送信
サービス提供者がGas 代を負担
署名
ユーザーが ETH を持っていなくてもサービスを利用できる
ETH を使わないサービスは特に UX 向上が見込める
サービス提供者が Gas 代を肩代わりできる
ユーザー
サービス提供者
Contract
Tx送信
署名
ちなみに、 ユーザーに Gas 代を払わせることもできる
盗難リスクを抑えたリカバリー機能の提供
通常のウォレットでは…
ユーザーが秘密鍵をなくしたらリカバリー不可能
…かといって、サービス提供側が秘密鍵を持つと、ハッキングによる盗難リスクが高まる
Dapper コントラクトウォレットでは…
ウォレットがコントラクトなので、秘密鍵はなく、独自のリカバリー機能を提供できている
サービス提供者の内部犯行がない前提なら、Recovery Key をコールドウォレットで管理できるため盗難リスクを抑えたリカバリー機能を実現できる
盗難リスクを抑えたリカバリー機能の提供
/// @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
コントラクトにユーザーが機能を追加できる
任意の関数とコントラクトアドレスを登録できる
例えば 新しいトークン規格が出てきても、同じコントラクトで対応できる(サポートしているインターフェースを返せる)
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 準拠
コントラクトにユーザーが機能を追加できる
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 関数に、追加した機能を呼び出すしくみが入っている
コントラクトにユーザーが機能を追加できる
追加した機能をサポートインターフェースとして返すしくみ
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;
}
その他開発関連・疑問点
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 }
疑問点
cosigner が address 型でなく uint256 型なのはなぜ?→ assembly でデータを処理するとき、mload だと 32 byte でしか読めないので、12 byte 分 左シフトしている. そのため、12 byte のダミーデータをつけられるように uint256 型にしている
Dapper ブラウザ拡張と MetaMask との違いはあるか?→ 開発者向けドキュメントによると、同じようにインテグレーションできる
関数シグネチャが同じで、異なるコントラクトの呼び出しを使い分けることはできない?→ できないはず
コントラクトにユーザーが機能を追加できるが、例えばどんなユースケースがあるか? → (いいユースケースがあれば知りたい)
参考
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
※間違っている点があればご指摘いただけると助かります。