Skip to content

refactor: add minimal reasoning verifier hook for ERC-8183#31

Open
ThoughtProof wants to merge 2 commits into
erc-8183:mainfrom
ThoughtProof:feat/reasoning-verifier-upstream
Open

refactor: add minimal reasoning verifier hook for ERC-8183#31
ThoughtProof wants to merge 2 commits into
erc-8183:mainfrom
ThoughtProof:feat/reasoning-verifier-upstream

Conversation

@ThoughtProof
Copy link
Copy Markdown

@ThoughtProof ThoughtProof commented Apr 17, 2026

Summary

This PR reintroduces the reasoning verifier hook on top of the current upstream architecture.

What changed

  • adds a minimal IReasoningVerifier interface
  • adds ReasoningVerifierHook built on BaseERC8183Hook
  • gates _preSubmit(...) using the ERC-8183 deliverable as the canonical hash
  • adds ThoughtProofReasoningVerifier as a reference implementation under contracts/examples/
  • adds tests covering the hook and the example verifier on the current upstream architecture

Design intent

The goal is to keep the core hook small, generic, and compatible with the current hook stack:

  • BaseERC8183Hook-native
  • verifier-agnostic
  • minimal surface area
  • product-specific example separated from the core hook

Canonical hash choice

This version treats the ERC-8183 deliverable as the canonical hash checked during _preSubmit(...).

That keeps the hook small and avoids adding extra hook-specific encoding requirements.

Verification

Local verification performed:

  • forge test --match-path test/ReasoningVerifierHook.t.sol
  • 'forge build' ✅

douglasborthwick-crypto added a commit to douglasborthwick-crypto/hook-contracts that referenced this pull request Apr 18, 2026
Adds a minimal ERC-8183 hook that gates the fund stage on a condition-based
wallet-state verifier. Complements existing score-based gating (TrustGateHook,
erc-8183#9/erc-8183#32) and content-based verification (ReasoningVerifierHook, erc-8183#31) with a
third shape: "does this wallet satisfy a named condition set right now?"

- contracts/interfaces/IWalletStateVerifier.sol
    Minimal (bool verified, uint256 validUntil) interface keyed on
    (wallet, conditionsHash). Hooks stay stateless views.

- contracts/hooks/WalletStateHook.sol
    Inherits BaseERC8183Hook + IERC8183HookMetadata. Immutable verifier +
    conditionsHash (deploy one hook per distinct condition set, mirrors the
    minConfidence immutable pattern in ReasoningVerifierHook). Overrides
    _preFund only — verifier.checkWalletState(caller, conditionsHash) →
    pass/fail + freshness, reverts otherwise.

- contracts/examples/InsumerWalletStateVerifier.sol
    Reference IWalletStateVerifier implementation. Relayer-push model with
    optional RIP-7212 P256VERIFY precompile verification of off-chain ECDSA
    P-256 (ES256) attestation signatures. Works on Base, Arbitrum, Optimism,
    Polygon, Scroll, ZKsync, Celo — standard ERC-8183 L2 footprint.

- test/WalletStateHook.t.sol
    21 tests, all passing. Covers constructor guards, _preFund happy path,
    not-verified revert, expired-attestation revert, validUntil boundary,
    selector isolation, ERC-165 interface support, verifier relayer auth,
    and signature-mode flag.

Stacked on top of erc-8183#30 (IACPHook → IERC8183Hook rename). Targets main;
will rebase cleanly once erc-8183#30 merges.
douglasborthwick-crypto added a commit to douglasborthwick-crypto/hook-contracts that referenced this pull request May 4, 2026
Adds a minimal ERC-8183 hook that gates the fund stage on a condition-based
wallet-state verifier. Complements existing score-based gating (TrustGateHook,
erc-8183#9/erc-8183#32) and content-based verification (ReasoningVerifierHook, erc-8183#31) with a
third shape: "does this wallet satisfy a named condition set right now?"

- contracts/interfaces/IWalletStateVerifier.sol
    Minimal (bool verified, uint256 validUntil) interface keyed on
    (wallet, conditionsHash). Hooks stay stateless views.

- contracts/hooks/WalletStateHook.sol
    Inherits BaseERC8183Hook + IERC8183HookMetadata. Immutable verifier +
    conditionsHash (deploy one hook per distinct condition set, mirrors the
    minConfidence immutable pattern in ReasoningVerifierHook). Overrides
    _preFund only — verifier.checkWalletState(caller, conditionsHash) →
    pass/fail + freshness, reverts otherwise.

- contracts/examples/InsumerWalletStateVerifier.sol
    Reference IWalletStateVerifier implementation. Relayer-push model with
    optional RIP-7212 P256VERIFY precompile verification of off-chain ECDSA
    P-256 (ES256) attestation signatures. Works on Base, Arbitrum, Optimism,
    Polygon, Scroll, ZKsync, Celo — standard ERC-8183 L2 footprint.

- test/WalletStateHook.t.sol
    21 tests, all passing. Covers constructor guards, _preFund happy path,
    not-verified revert, expired-attestation revert, validUntil boundary,
    selector isolation, ERC-165 interface support, verifier relayer auth,
    and signature-mode flag.

Stacked on top of erc-8183#30 (IACPHook → IERC8183Hook rename). Targets main;
will rebase cleanly once erc-8183#30 merges.
@ariessa
Copy link
Copy Markdown
Collaborator

ariessa commented May 4, 2026

Hi @ThoughtProof,

  1. This PR is outdated. Can you pull current code from main branch into this PR?
  2. This PR is kinda bloated. Please keep it to hook contract only. Can you remove other unnecessary files and move interface inside hook contract?

@ThoughtProof ThoughtProof force-pushed the feat/reasoning-verifier-upstream branch from c1c2ff3 to b112299 Compare May 4, 2026 18:08
@ThoughtProof
Copy link
Copy Markdown
Author

Rebased on current main and addressed both points:

  1. Rebased — single clean commit on top of current main.
  2. Scope reduced — removed ThoughtProofReasoningVerifier.sol (product-specific example), removed IReasoningVerifier.sol (interface now inlined in the hook contract), removed all changes to upstream contracts (BaseERC8183Hook, BiddingHook, FundTransferHook, MultiHookRouter, erc8183 submodule).

What remains:

  • contracts/hooks/ReasoningVerifierHook.sol — 63 lines, interface inlined, gates _preSubmit via an external IReasoningVerifier
  • test/ReasoningVerifierHook.t.sol — 13 tests using a mock verifier (no product-specific dependencies)

forge build ✅ · forge test 13/13 ✅

Copy link
Copy Markdown
Collaborator

@ariessa ariessa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @ThoughtProof,

Thanks for working through the previous round of feedback. One housekeeping thing first: could you remove the test file from the PR? On the contract itself, I spent more time with it on this pass and there are a few design points I'd like to walk through before we land it.

Problem

_preSubmit checks the deliverable hash against the verifier, but the hash isn't tied to the job, the worker, or the requester. So:

  • Worker A finishes Job 1, their hash H gets marked verified.
  • Worker B copies H from chain and submits it for Job 2.
  • The hook approves both. Worker B did no work.

It also has no consumption step. The same verified hash works for unlimited submissions.

This is fine if the verifier is run by the requester themselves, but the NatSpec frames it as a general "reasoning verifier," which is exactly the case where this breaks.

Suggestions

  1. Bind verification to the job. Change verifyReasoning to take (jobId, worker, deliverable). _preSubmit already has all three — pass them through. This closes the cross-job replay.
  2. Make it single-use. Either the verifier consumes the record after a read, or the hook stores mapping(uint256 => bool) so the same job can't replay. Means _preSubmit is no longer view.
  3. requiredSelectors() should return the submit selector. Right now it returns [], which tells the router the hook gates nothing.
  4. Emit an event with (jobId, deliverable, confidence) so there's an on-chain trace.
  5. Cap returned confidence at 1000. A bad verifier returning type(uint256).max would still pass.
  6. If you have a use case in mind that justifies the current shape, walk me through it. Either way, please add this to the NatSpec:
    • Use case — what problem does the hook solve, who deploys it?
    • Flow — end-to-end happy path: where does the hash come from, who calls the verifier?
    • Trust model — who operates the verifier, and what attacks does this hook explicitly not defend against?

…eliverable)

Addresses all 6 review items from @ariessa (erc-8183#31):

1. Cross-job replay: verifyReasoning now takes (jobId, caller, deliverable)
   instead of bare canonicalHash. Prevents hash reuse across jobs.

2. Single-use: _consumed mapping per (jobId, caller) prevents replay.
   _preSubmit is no longer a view function.

3. requiredSelectors(): returns [submit_selector] instead of empty array,
   so the router correctly dispatches to this hook.

4. Event emission: ReasoningVerified(jobId, caller, deliverable, confidence)
   emitted on every successful verification.

5. Confidence cap: values above MAX_CONFIDENCE (1000) are capped, not rejected.

6. NatSpec: added Use Case, Flow (7-step happy path), Trust Model, and
   Caller Semantics documentation to the contract header.

Additional:
- Fixed parameter name: caller (not worker) to match BaseERC8183Hook
  signature. caller = msg.sender on AgenticCommerce.submit(), which may
  be the worker or an authorized operator.
- _consumed scoped per (jobId, caller) to allow competitive submissions
  from different callers on the same job.
- 13 tests covering all security scenarios.
@ThoughtProof
Copy link
Copy Markdown
Author

Thanks for the thorough review @ariessa — pushed 879c0f8 addressing all six items.

Changes:

  1. Cross-job replayverifyReasoning now takes (jobId, caller, deliverable) instead of a bare hash. Verification is bound to the full submission context.

  2. Single-use — Added mapping(uint256 => mapping(address => bool)) _consumed in the hook. _preSubmit is no longer a view. Scoped per (jobId, caller) so competitive submissions from different callers on the same job are still possible if independently verified.

  3. requiredSelectors() — Returns [submit_selector] now.

  4. EventReasoningVerified(jobId, caller, deliverable, confidence) emitted on every successful gate.

  5. Confidence cap — Capped at MAX_CONFIDENCE = 1000. Values above are silently capped rather than reverted (debatable — happy to change to a hard revert if you prefer).

  6. NatSpec — Added Use Case, Flow (7-step happy path), Trust Model, and Caller Semantics sections to the contract header.

Additional fix: Renamed the second _preSubmit parameter from worker to caller to match the BaseERC8183Hook signature. In ERC-8183, caller is msg.sender on AgenticCommerce.submit() — which may be the worker or an authorized operator. The NatSpec now documents this explicitly.

Test coverage: 13 tests covering deployment, happy path, cross-job replay, cross-caller replay, single-use enforcement, confidence cap, selector registration, and non-submit passthrough.

Let me know if anything needs further adjustment.

douglasborthwick-crypto added a commit to douglasborthwick-crypto/hook-contracts that referenced this pull request May 14, 2026
Adds a minimal ERC-8183 hook that gates the fund stage on a condition-based
wallet-state verifier. Complements existing score-based gating (TrustGateHook,
erc-8183#9/erc-8183#32) and content-based verification (ReasoningVerifierHook, erc-8183#31) with a
third shape: "does this wallet satisfy a named condition set right now?"

- contracts/interfaces/IWalletStateVerifier.sol
    Minimal (bool verified, uint256 validUntil) interface keyed on
    (wallet, conditionsHash). Hooks stay stateless views.

- contracts/hooks/WalletStateHook.sol
    Inherits BaseERC8183Hook + IERC8183HookMetadata. Immutable verifier +
    conditionsHash (deploy one hook per distinct condition set, mirrors the
    minConfidence immutable pattern in ReasoningVerifierHook). Overrides
    _preFund only — verifier.checkWalletState(caller, conditionsHash) →
    pass/fail + freshness, reverts otherwise.

- contracts/examples/InsumerWalletStateVerifier.sol
    Reference IWalletStateVerifier implementation. Relayer-push model with
    optional RIP-7212 P256VERIFY precompile verification of off-chain ECDSA
    P-256 (ES256) attestation signatures. Works on Base, Arbitrum, Optimism,
    Polygon, Scroll, ZKsync, Celo — standard ERC-8183 L2 footprint.

- test/WalletStateHook.t.sol
    21 tests, all passing. Covers constructor guards, _preFund happy path,
    not-verified revert, expired-attestation revert, validUntil boundary,
    selector isolation, ERC-165 interface support, verifier relayer auth,
    and signature-mode flag.

Stacked on top of erc-8183#30 (IACPHook → IERC8183Hook rename). Targets main;
will rebase cleanly once erc-8183#30 merges.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants