メインコンテンツまでスキップ

ワークドエグザンプル: 1 つのプロパティを最後まで追う

SPECA が何をしているかを掴む最短ルートは、1 つのプロパティが仕様の文から結晶化し最終的に 04_PARTIAL_*.json の verdict に至る過程を追うことです。本ページではそれを、各ステップでリアルな JSON 例を添えて行います。番号は内部フェーズ ID を使います。論文 Phase 1〜6 とのマッピングは パイプライン概要 を参照してください。

出発点 — 仕様の 1 文

EIP 風ドキュメントに次の 1 行があるとします:

"MUST verify the validator's signature on every block before applying it to the local state."

この 1 文が型付きプロパティとなり、最終的に監査 verdict になります。

Phase 01a (Spec Discovery)

クローラが該当セクションを発見しインデックス化します。

{
"url": "https://eips.example.org/EIP-9999.md",
"title": "EIP-9999 — Block Application",
"section_id": "block-application",
"section_text": "MUST verify the validator's signature on every block before applying it to the local state.",
"links": ["https://...consensus-spec...", "..."]
}

出力: outputs/01a_STATE.json — 発見した仕様ページのインデックス。

Phase 01b (Subgraph Extraction)

セクションが状態遷移フラグメントになり、RFC 2119 不変条件が紐付きます。

stateDiagram-v2
[*] --> BlockReceived
BlockReceived --> SignatureVerified: verify_block_sig()
SignatureVerified --> StateApplied: apply_block()
StateApplied --> [*]

note right of BlockReceived
INV-001: signature MUST be verified before state application
end note

JSON 表現:

{
"spec_section_id": "FN-042",
"spec_text": "MUST verify the validator's signature on every block before applying it to the local state.",
"subgraph": {
"states": ["BlockReceived", "SignatureVerified", "StateApplied"],
"transitions": [
{"from": "BlockReceived", "to": "SignatureVerified", "label": "verify_block_sig()"},
{"from": "SignatureVerified", "to": "StateApplied", "label": "apply_block()"}
],
"invariants": ["INV-001"]
},
"mermaid_file": "outputs/graphs/FN-042.mmd"
}

出力: outputs/01b_PARTIAL_*.json

Phase 01e (Property Generation)

STRIDE + CWE Top 25 が不変条件を型付きセキュリティプロパティに変えます。

{
"property_id": "PROP-101",
"type": "Invariant",
"description": "Signature on a block MUST be verified before its state transition is applied.",
"covers": "FN-042",
"classification": "STRIDE_Tampering",
"cwe_related": ["CWE-345", "CWE-347"],
"reachability": {
"classification": "PUBLIC_API",
"entry_points": ["process_block()", "on_block()"],
"attacker_controlled": ["block.signature", "block.body"],
"bug_bounty_scope": "in_scope"
}
}

出力: outputs/01e_PARTIAL_*.jsoncovers が単一文字列 (主要仕様要素 ID) なのは slim な 01e スキーマ判断 のとおりです。

Phase 02c (Code Pre-resolution)

tree_sitter MCP がプロパティを対象リポジトリの具体コード位置に解決します。

{
"property_id": "PROP-101",
"type": "Invariant",
"description": "Signature on a block MUST be verified before its state transition is applied.",
"code_scope": {
"resolution_status": "resolved",
"locations": [
{
"file": "consensus/src/block.rs",
"symbol": "BlockProcessor::process",
"line_range": [120, 198],
"role": "primary",
"note": "Caller invokes apply_block before verify_block_sig in the cache-hit branch — verify in Phase 03."
},
{
"file": "consensus/src/sig.rs",
"symbol": "verify_block_sig",
"line_range": [42, 68],
"role": "callee"
}
]
},
"severity": "HIGH"
}

出力: outputs/02c_PARTIAL_*.json。primary location の note は Phase 02c が立てた仮説です — Phase 03 が検証または反証する必要があります。これが 事前解決 → 監査の引き渡し です。

Phase 03 (Audit — Map → Prove → Stress-Test)

エージェントが実装でプロパティが成立する証明を試みます。

{
"property_id": "PROP-101",
"verdict": "FINDING",
"proof_attempt": {
"claim": "verify_block_sig() is always called before apply_block() on every reachable path of process()",
"map": {
"primary_symbol": "BlockProcessor::process",
"relevant_blocks": ["validate", "cache_check", "apply"]
},
"prove": {
"happy_path": "verify_block_sig at line 142 → apply_block at line 188 — proof closes",
"cache_hit_path": "cache lookup at line 165 returns BlockState::Verified, jumps to line 188 directly without verify_block_sig",
"proof_gap": "Cache stores blocks marked Verified by an earlier process() call but does not re-verify after a chain re-org. After re-org, a previously-Verified block may be applied without verification."
},
"stress_test": {
"counterexample_constructed": true,
"scenario": "Reorg between block N and block N+1 where N is in the cache; N is replayed and apply_block runs without re-verification."
},
"confidence": "HIGH"
}
}

出力: outputs/03_PARTIAL_*.json。フローは Phase 03 の制御フロー を参照。

Phase 04 (Review — 3 ゲート FP フィルタ)

finding が Dead Code → Trust Boundary → Scope を順に通ります。

{
"property_id": "PROP-101",
"finding_id": "FINDING-018",
"verdict": "CONFIRMED_VULNERABILITY",
"severity": "HIGH",
"gate_results": [
{"gate": "dead_code", "passed": true,
"rationale": "process() is reachable from the public p2p handler"},
{"gate": "trust_boundary","passed": true,
"rationale": "block.signature and block.body are attacker-controlled inputs from the network"},
{"gate": "scope", "passed": true,
"rationale": "consensus/src/block.rs is in_scope per BUG_BOUNTY_SCOPE.json"}
]
}

出力: outputs/04_PARTIAL_*.json。これが speca browse に最終的に表示される行です。

トレースできるもの

speca browse に並ぶ行については、プロパティ → サブグラフ → 仕様セクションが復元可能です:

04 verdict (FINDING-018)
└─ property_id: PROP-101
└─ covers: FN-042 (01e → 01b)
└─ spec_section_id: block-application (01b → 01a)
└─ url: eips.example.org/EIP-9999.md

このトレーサビリティが仕様駆動アプローチの最大の見返りです: 各 finding (および各 FP) が、それを生んだ判断の連鎖を本物の仕様の 1 文まで持って戻れます。