Crypto OS
Technical Crypto OS第5段階 · インフラとセキュリティ

T27 · Account Abstraction

利用者が秘密鍵を直接持たなくても、安全に使えるか?

この章を 3D で体験する →
練習する能力
BuilderProtocol Literacy
実践
スマートアカウントをデプロイし、セッションキーとガス代の肩代わりを設定して、ユーザーのガス代ゼロの操作を一度完了する。
AI Lab
AI にソーシャルリカバリーの手順を設計させ、悪用される 3 つのケースを自分で書く。

現実の問題

軽いオンチェーンアプリを作りました。機能自体は三回クリックで使えます。

新規利用者の実際の流れはこうです。

1. wallet extensionを入れる
2. 12語のseed phraseを写し、順番を確認する
3. native tokenを買い、最初のGasを用意する
4. 開始を押し、署名popupで確認する
5. 次へ進み、また署名する
6. さらに次で、もう一度署名する

100人の訪問から、wallet導入30人、seed保存15人、Gas保有5人、初回完了2人になります。

機能ではなく、オンチェーンのアカウントになる段階で離脱しています。

三か月後、完了した二人の一人が、端末変更でwalletを失い、メモのseedも見つからず、復旧を頼みます。

できないと答えるしかありません。

両方の原因は、EOAでは秘密鍵がアカウントの全てだからです。持てば全権、失えば何もありません。

ゲームだけ、少額だけ、所定手順で復旧という範囲がありません。

全か無かを分解できるでしょうか?

思考実験

二種類を比べます。

第一、一鍵だけの金庫です。

鍵の持ち主が全権を持ち、用途で分けられず、紛失届も出せません。なくせば永久に開かず、盗まれれば中身を失います。

資金を動かせる人は物理的事実です。

第二、会社の銀行口座です。

鍵一つでなく規程があります。

- 担当者は5,000元まで
- 財務責任者は50,000元まで
- 500,000元超は二人の承認
- 家賃と光熱費は定期払い
- 印章紛失は公告、異議期間、再発行

金額別、用途別、複数人、紛失復旧を実現します。

理由は一つです。

金庫の権限は物理的事実、会社口座の権限は読み、変更し、監査できる規則です。

Account Abstractionは規則をコードにします。公開鍵由来の住所ではなく契約になり、この操作を認めるかを関数が答えます。

しかし規則がコードになると、新しい問題があります。

規則を変える権限は誰のものか?

鍵喪失に加えて、規則を変更される失敗が生まれます。

リスクは物理問題からガバナンス問題へ移り、後半ではそれを扱います。

あなたが決める

漏斗の改善を一つ選びます。

結果を観察する

比較します。

初回負担紛失単一点日常新しい責任
EOAと案内高い復旧不可seed毎回署名なし
custody最小運営に依頼運営意識不要全資産保管
smartとsession低い設計次第契約規則session中は不要安全とGas
複数鍵中設計次第可用性の問題毎回協力調整

第三・四列から次が分かります。

鍵を消すのではなく、一鍵の全権を複数鍵の異なる権限と復旧経路へ分解します。

権限という概念がアカウント層に生まれます。

同時に次も必要です。

分割した権限の境界は、新しい攻撃面です。

EOAの鍵に加え、コード、session範囲、代付規則、復旧、それらを変える人があります。

問題の場所を移すことで、監査、テスト、assertionが可能になります。 seed喪失は検証できませんが、範囲誤りはできます。

T32 と同じく、policy engineを契約へ移します。

モデルを作る

検証と実行を分ける

全設計の出発点です。

validate(op) → 操作を認めるか。規則を確認する
execute(op)  → 検証を通った操作だけを行う

EOAでは署名成功と実行が直結します。smartでは判断を自分のコードにできます。

一操作の全経路

利用者は直接取引でなく操作意図を署名します。

  1. 意図を署名
  2. 専用poolへ
  3. bundlerがまとめて送信
  4. EntryPointが配分
  5. accountが検証
  6. paymasterが支払判定
  7. accountが実行
意図の署名者、取引の送信者、Gasの支払者を分離します

EOAにはない三役です。

役仕事障害
bundler意図を取引へまとめる停止するが偽造しない
EntryPoint配分と課金全accountの共通依存
paymasterGasを負担広い規則で引き出され続ける

前章と同じ層の区別です。bundlerは伝送層で、到着にだけ影響し、真実性には影響しません。 数が多いので安全という説明は、層を取り違えています。

accountの核心

重要部分です。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

contract SmartAccount {
    address public owner;                 // 主鍵
    uint256 public nonce;                 // replay防止
    address public immutable entryPoint;  // 唯一の入口

    struct SessionKey {
        uint48  validUntil;    // 期限
        uint128 spendLimit;    // 累計上限(最小単位)
        uint128 spent;         // 使用済み
        bool    enabled;
    }

    // session鍵から範囲
    mapping(address => SessionKey) public sessions;
    // session鍵、target、selectorごとの許可
    mapping(address => mapping(address => mapping(bytes4 => bool))) public allowed;

    constructor(address ep, address o) {
        entryPoint = ep;
        owner = o;
    }

    modifier onlyEntryPoint() {
        require(msg.sender == entryPoint, "not entry point");
        _;
    }

    /// 検証は判断だけ。外部呼出をしない
    function validate(
        bytes32 opHash,
        bytes calldata signature,
        address target,
        bytes4  selector,
        uint256 value
    ) external onlyEntryPoint returns (bool) {
        address signer = recover(opHash, signature);

        // 主鍵は無制限
        if (signer == owner) return true;

        // sessionの範囲を一項ずつ確認
        SessionKey storage s = sessions[signer];
        if (!s.enabled) return false;
        if (block.timestamp > s.validUntil) return false;
        if (!allowed[signer][target][selector]) return false;
        if (value + s.spent > s.spendLimit) return false;

        // 検証時に累計を消費し、同ブロックの重複利用を防ぐ
        s.spent = uint128(s.spent + value);
        return true;
    }

    /// 検証済みと入口が判断した後に実行
    function execute(address target, uint256 value, bytes calldata data)
        external
        onlyEntryPoint
    {
        (bool ok, bytes memory ret) = target.call{value: value}(data);
        if (!ok) {
            assembly { revert(add(ret, 32), mload(ret)) }
        }
    }

    function recover(bytes32, bytes calldata) internal pure returns (address) {
        return address(0); // frameworkの署名復元に置換
    }
}

四つの判断が事故に対応します。

第一、onlyEntryPoint。 制限がなければ誰でもexecuteで送金できます。T7 の公開入口の原則です。

第二、累計を検証時に引きます。 後なら同ブロックでspent旧値を繰り返し使えます。使用後の更新というreentrancyの形です。

第三、検証は外部呼出しません。 offchain simulationと実状態の差で失敗すると、bundlerが無駄なGasを払い、繰り返せばサービスしなくなります。

第四、実行失敗の元errorをそのまま返します。 T14 の複数動作に一 status しかない問題は、内側のrevert dataを変更せず上へ伝えることで解きます。省くとUIは取引失敗だけを表示し、どの段階か永遠に説明できません。

session:範囲が全て

弱い鍵ではなく、範囲付きの鍵です。

六項目が必要です。

次元省くと
期限漏えいが永久有効
target任意契約へ
selectorapproveも可能
一回上限一度で空にする
累計小分けで回避
再授権自分で別sessionを作る

第三行を詳しく見ます。対象契約だけを制限し関数を制限しないと、ゲームだけのsession keyがゲーム契約の承認関数を呼び、攻撃者管理のaddressへ枠を渡して、後で外から徐々に引き出せます。

T32 の授権経由の例と同じです。

送金だけでなく、資金の制御権を変える全操作をpolicyで覆います。

代付:支払側もpolicyが必要

無制限のスポンサーは継続して消費できるpoolです。

三方式です。

方式負担者リスク
全額スポンサーproject無意味な高Gas
別token払いuseroracle操作、transfer hook
初回N回だけproject、上限あり一人の多数account

回数、額、日予算、target allowlist、即時停止が必要です。

表の第二行のtransfer hookは T9 の話です。非標準tokenの transfer は外部へ実行権を渡し、受取時に注意しないPaymasterの核心経路へ外部callを持ち込みます。次章でその結果を完全に実験します。

復旧:最も危険な節

基本手順です。

1. N guardian、threshold Mを事前設定
2. M署名で主鍵変更を提案
3. timelock T日
4. 現主鍵は期間中いつでも取消
5. 取消なしなら有効化

待機と取消がなければguardianは乗っ取り鍵です。

紛失だけでなく悪用も考えます。

悪用内容timelock
共謀thresholdで奪う本人の取消が必要
非独立三住所が同一人防げない
社会工学本人を装う本人の取消頼み
本人不在入院、旅行、死亡など構造的盲点
guardian紛失Mへ届かない可用性
owner逆悪用譲渡約束後に取り戻すガバナンス

第四行は重要です。timelockは、期間中に利用者が通知を見て取消できることが全前提です。鍵を失う原因が正常なnetwork利用もできなくする出来事なら、最も復旧を必要とする場面でこの前提は成立しません。

三つが重要です。

  1. eventとoffchain通知を出します。 見えない提案には待機の意味がありません。
  2. 取消は無条件・即時です。 追加要件を付けません。
  3. guardian独立性を確認します。 方法と確認日を残します。

timelockの長さは技術定数でなく、製品上のトレードオフです。 短ければ共謀を防げず、長ければ正当な復旧者を待たせます。accountの金額によって期間を変えます。T17 の業務risk別確認深度と同じ発想です。

何と呼ぶか

スマートアカウントSmart Account

署名を認めるかをコードが決める契約accountです。

EOAの暗号学的事実を、読めて検証できる規則へ変えます。

操作意図UserOperation

target、data、value、Gas上限、paymasterを持つ構造化意図です。

送信者とGas支払者を分けられます。

バンドラーBundler

意図をまとめて取引として送ります。

止められても偽造はできません。 事前検証をsimulateするためvalidateが制限されます。

入口契約EntryPoint

配分・課金の共通入口です。

失敗すると全accountに及ぶため厳格な監査が必要です。

代付契約Paymaster

スポンサーや別tokenでGasを払います。

いつ拒否するかを決めなければ、誰でも使えるATMです。

セッション鍵Session Key

期限、target、selector、一回・累計上限を持つ一時鍵です。

範囲が全てです。 targetだけでは授権で額を回避できます。

ソーシャル復旧Social Recovery

事前guardianで主鍵を替えます。

成立にはtimelock、chain上eventとchain外通知、現主鍵の無条件取消権の三つが必要です。一つでも欠けると、guardianは復旧の協力者からaccountを奪える人へ変わります。

実践

実践smart accountにsessionと代付を設定し、利用者Gasゼロで操作するSolidity framework、local nodeかtestnet無料。faucetだけを使い、mainnetや実資産に使いません

localかtestnetだけです。 意図的な欠陥を作り修正します。

四つのred/green基準です。

1. 主鍵は任意操作
2. sessionは範囲外をrevert
3. native残高ゼロでも成功
4. 授権で額上限を回避できない

主鍵だけの最小accountを作ります。

SmartAccountをowner、nonce、validate、executeへ簡略化します。

最初は負のテストで、無関係な住所からexecuteしてrevertを要求します。

機能より先に書きます。 第一の要件はアクセス制御です。

EntryPointで全経路を通します。

意図作成、署名、bundler、inclusionを観察します。

三問です。

- 取引fromは誰か?
- Gasはどこから引くか?
- validateは何回呼ばれたか?

利用者addressは取引の送信者fieldへ現れなくなります。 これが最初の問いから分かる大きな変化です。indexer、risk判定、送信者でfilterするqueryの全てを合わせて変更します。

targetだけを制限したsessionを追加します。

他contractの呼出が拒否されることを確認します。

重要な攻撃実験として、許可されたアプリの授権関数で自分の別addressへ権限を渡し、引き出します。

selectorなしでは額上限を回避できます。

実際に成功させて意味を理解します。

selectorと累計を加え、攻撃を失敗させます。

修正前は攻撃成功、修正後は拒否となる同じ試験を使います。

コンパイルだけでは修正確認になりません。

累計を引く時点を確認します。

一取引で繰り返し、小額合計を上限以上にします。

spentが後更新なら攻撃が成功します。validate時へ移して拒否します。

使用後に更新する形を覚えます。

代付でGasゼロにします。

まず無制限でゼロ残高の成功を確かめます。

すぐ回数、日予算、target、停止を追加します。

高Gasの連続無意味操作を拒否する試験を行います。

復旧を作って自分で攻撃します。

guardian、threshold、待機、取消を実装し、三試験を行います。

- 期間内の取消で無効化
- 取消なしなら有効化
- eventに提案者、新owner、有効時刻

最後は本人が乗っ取りを知れるかの根拠です。

権限台帳を作ります。

誰操作上限期限取消
主鍵任意なし永続復旧
sessionallowlist関数一回・累計expiry主鍵即時
guardianowner変更提案変更だけ常駐主鍵期間内
paymasterGas日予算常駐緊急停止

コード根拠を示します。空欄は未設計の権限です。

AI Lab

AI LabAIの復旧設計へ、再現可能な悪用を三つ書くLevel 2 · AI Copilot

第三段階が本題です。

第一:復旧の全状態、遷移event、各役の操作をcontract関数単位で設計してください。
第二:「___の不正・失敗でaccountが___される」と、役割と人数で仮定を列挙してください。
第三:奪取を狙う攻撃者として六経路以上を示し、
前提、手順、防げない段階、本人の発見可能性を記してください。
社会工学と時機も含めます。

第一は標準解のある問題で、待機と奇妙な取消条件を見ます。

第二段階で理解を確かめます。「guardianが不正なら奪われる」は、正しくても情報量がありません。何人、どの時間窓、ownerはそのとき何をしているかを追加して初めて、使える信頼仮定になります。

第三段階がLabの価値で、経路の列挙はモデルの得意分野です。出張中に開始、偽の安全upgrade通知で本人に承認させる、通知channelを先に侵害して復旧、guardianの端末交換まで潜伏して入り込むなど、具体的な案を示します。

自分で二つ加えます。

第一、guardian非独立。五人に見えて三住所が同一人ならthresholdが崩れます。

第二、ownerの逆悪用。譲渡済みaccountの奪還は共同管理で実在するガバナンスリスクです。

最後は自分で三つの悪用を動くテストにします。説明だけの攻撃と成功する攻撃テストでは、信頼度が一桁違います。T8 からの同じ基準です。

AI の回答後、自分で必ず検証すること

  • timelockがあるか
  • 主鍵の取消が無条件・即時か
  • eventと通知を届けるか
  • 同一人の複数guardianを考慮したか
  • 本人不在という盲点を認めたか
  • guardian鍵喪失でthreshold不足を考えたか
  • 待機期間を金額に応じて設計したか
  • 三悪用を実行可能テストへする

実例

未初期化accountが先取りされた繰り返し起きる形

多くのsmart accountは、先に最小proxyを公開し、利用者が後からowner設定の初期化を呼びます。callerを制限しない初期化では、その間に誰でも自分をownerにできます。

大量部署では数分の公開窓になります。

T8 の2017年libraryと同じ、無防備な初期化の形です。 deploymentと初期化を一取引にまとめ、初期化関数にも一回だけという確認を入れます。

高Gas操作でスポンサーを枯らすよくある運用事故

全利用者へ無制限でGasを提供しました。

scriptが無意味な高Gas操作を反復し、数時間で空になりました。

盗むのでなく、運営へ焼かせた事故です。 回数、額、日予算、targetで防ぎます。

他人への支払いには拒否規則が必要です。

targetだけ制限しselectorを制限しない代表的な実装欠陥

ゲーム限定のsessionに小額上限がありました。

攻撃者は送金でなく授権関数で大allowanceを与え、別住所で引き出しました。

approveのvalueはゼロなので制限に触れません。

T32 と同じ送金だけの上限では制御権変更を防げません。

chain IDのない署名が別chainで再実行多チェーン部署

同じaddressを複数chainに置き、chain IDを署名へ入れませんでした。

Aの署名をBで再利用できました。

前章のクロスチェーンmessage IDへ宛先chainを含める規則と同じです。検証する署名は、chain、契約address、nonceの適用範囲へ全て結び付けます。

変数を一つ変える

もしsessionが一時間から30日へ

月間の再授権を省けます。

漏えい後の露出期間は720倍になります。その間にアプリがupgradeして、元の権限範囲で考えていない関数を追加する可能性があり、利用者も何を許可したか忘れているという、三軸のriskが増えます。

累計と狭いselectorで補い、残り期間と範囲を見せます。

期限と範囲は釣り合いが必要です。

もし五人の友人を自分の三端末に替える

共謀と社会工学は減ります。

可用性riskは増えます。同じ家の三端末は、一火災、引っ越し、盗難で全て失い得ます。 guardianが同時に失敗しないというsocial recoveryの仮定を、同じ場所の端末が破ります。

同じ引き出しに三鍵を置いた金庫へ戻ります。

もし別tokenでGasを払う

native購入を省けます。

新たな依存は二つです。価格はtokenがGas何単位に相当するかを決め、操作されると安すぎる支払を受けてしまいます。T21 の経路です。token自身も T9 のhook、送金税、特定条件のsilent failureを持ち込み、全てPaymasterのriskになります。

対応tokenはallowlistで決めます。

もしaccountがupgradeable

修正、機能、新標準へ資産移動なしで対応できます。

accountに全規則を越える権限を加えます。実装をupgradeできる人は、全検証ロジックを一度に変えられます。 session範囲、復旧timelock、Paymasterのallowlistも全て無効にできます。

T8 の三原則を全て守り、upgrade権限はアプリ開発者でなくaccount owner自身へ置きます。 開発者が持つと、利用者資産の安全性が開発者の秘密鍵に依存し、明示していないcustodyへ戻ります。

持ち帰る問い

4
ユーザーは誰か?

誰が利用者かが技術選択を決めます。crypto経験者ならEOAと良い案内で十分かもしれません。秘密鍵を初めて聞く人にはseedを書き写すことが壁です。新技術のためでなく、その具体的な人のために選びます。

5
誰が支払っているか?

誰が払うか?送信者とGas支払者を分けると、Paymasterの財源、拒否条件、枯渇後も製品を使えるかを改めて答える必要があります。答えのないスポンサー案は、公開後の運用事故になります。

16
エージェントにはどんな権限があるか?

期限、target、selector、一回、累計、再授権の六項目を契約へ書きます。 T32 のoffchain policyと両方使います。

章末チェック

要点

アカウント抽象化は「誰が署名できるか」をプログラム可能なポリシーに変える。

この章の実践課題を終えたらチェックしてください進捗をすべて見る

このページの内容