Skip to content

About

Offline pre-signing intent analysis for Ethereum and a self-hosted co-signer callback handler (Fireblocks API Co-Signer)

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

contractscan-guard

Offline pre-signing intent analysis for Ethereum, and a self-hosted co-signer callback handler built on it. Python >= 3.11, MIT licence.

This repository is extracted from a larger private toolkit (contractscan). It contains the pre-signing layer and the co-signer plugin only.

What it does

  • Intent analysis (contractscan_guard.drainers, contractscan_guard.guard): decodes what a transaction or a signature request actually authorises -- approve, permit, Permit2, setApprovalForAll, transfers, multicall, Seaport orders, EIP-7702 authorisations, EIP-712 (Permit, Permit2, Seaport, SafeTx), SIWE and eth_sign -- and returns findings together with explicit checked / not_checked lists. It never says "safe". Address-poisoning checks (lookalike addresses against an address book) and an offline threat-feed loader are included.
  • Co-signer plugin (contractscan_guard.cosigner): a policy engine for institutional signing hooks. It implements the Fireblocks API Co-Signer callback (/v2/tx_sign_request, JWT RS256 both ways) and answers APPROVE, REJECT or RETRY (escalation to a human) with reasons. It unfolds Safe execTransaction (including delegatecall), MultiSend, Multicall3, Safe administration (owners, threshold, modules, guards, fallback handler, singleton changes), ownership transfers, proxy upgrades, role grants and EIP-7702, and cross-checks the signed digest, rawTx, contractCallData and destAddress against each other. It has shadow and enforce modes, a JSONL decision log and an override file for answering escalations. Utila and Cordial adapters are sketches only: their request schemas are not public.

Offline by design. The decision path makes no network call: everything the engine knows about the world (address book, code snapshot, block lists, feed snapshots, delegatecall and 7702 allowlists) is loaded from files at start-up. tests/cosigner/test_offline.py replaces every socket entry point with an exception and runs all request shapes through the engine.

It is not a simulator (nothing is executed) and not a reputation service (it reads only what the operator put on disk). Details, the Fireblocks callback contract as documented publicly, the default policy and known limits: docs/cosigner-plugin.md.

Install and run

python -m pip install -e ".[dev]"

contractscan-guard cosigner keygen --out keys/              # handler key pair (RSA 2048)
contractscan-guard cosigner serve -c examples/cosigner.example.toml   # mode = "shadow"
contractscan-guard cosigner check request.json --json       # one offline decision
contractscan-guard cosigner approve <txId> -o cosigner-overrides.json  # answer an escalation
contractscan-guard feeds sync                               # optional: threat feeds into a local cache

As a library:

from contractscan_guard.cosigner import Engine, Knowledge

engine = Engine(knowledge=Knowledge())
record = engine.decide_fireblocks(tx_sign_request_body)
print(record.decision, record.reasons)

Tests

python -m pytest

225 tests, no network (local mock servers only).

Benchmarks (reproducible from public data)

The benchmark datasets are not committed. The collectors rebuild them from public sources and cache every HTTP response, so reruns are deterministic and offline:

# Real Safe treasury flow and incidents: Safe Transaction Service, public RPC, post-mortems
python analysis/cosigner_real_collect.py          # network; writes analysis/cosigner-real/
python analysis/cosigner_real_benchmark.py        # offline replay

# Retail approvals corpus: Etherscan phish-hack labels (forta-network/labelled-datasets),
# on-chain Approval/Permit events, polkadot-js phishing list, DefiLlama
python analysis/guard_corpus_collect.py           # network; writes analysis/guard-benchmark/collected.json
python analysis/guard_benchmark.py build          # hand-built cases -> corpus.json
python analysis/guard_benchmark.py run
python analysis/cosigner_benchmark.py             # co-signer over both corpora

The sources of every dataset, their licences and the selection rules are documented in the collectors' docstrings. Two small reference tables are committed because the tests check the built-in constants against them: analysis/cosigner-real/safe-deployments.json (the safe-global/safe-deployments registry) and analysis/cosigner-real/fireblocks-assets.json (Fireblocks' public "Supported Networks" table matched to chainid.network).

Results at the time of writing (October 2026)

Enforce mode, default policy, nothing tuned on these numbers, 95% Wilson intervals.

  • Retail approvals corpus, frozen held-out half (262 legitimate, 306 malicious): with a code snapshot, legitimate REJECT 0% (0-2%), malicious REJECT 83% (78-87%); with no knowledge at all, legitimate RETRY 36% (31-42%) and no REJECT.
  • Real treasury flow: 25 public DAO treasury Safes of 6 organisations, 4,393 executed transactions replayed as both owner confirmations and executions. With a code snapshot and an address book seeded from each Safe's earlier history: REJECT 0.5% (0.3-0.7%), RETRY 5.3% (4.6-6.0%). All 21 REJECTs were reviewed by hand and are policy hits (enableModule of a new module, removeOwner down to threshold 1, delegatecall to unverified code).
  • Real incidents (malicious SafeTxs identified by hash, safeTxHash reproduced): Bybit REJECT, WazirX REJECT, Radiant RETRY.
  • Latency: engine decision p50 about 0.1 ms; full HTTP round trip with JWT RS256 both ways p50 about 6-10 ms, p99 about 30-55 ms on a desktop CPU.

Caveats: DAO treasury flow is not a custodian's flow; the code snapshot is the current one, not historical; transactions are clustered by Safe, so the intervals are optimistic; EIP-7702 and claimed-vs-raw mismatches are covered only by constructed scenarios. A specific operator's escalation rate can only be measured in a shadow pilot.

Licence

MIT, see LICENSE. The bundled OFAC SDN address list is US government public data.

About

Offline pre-signing intent analysis for Ethereum and a self-hosted co-signer callback handler (Fireblocks API Co-Signer)

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages