Skip to content

feat: signed cross-provider verification with AirnodeHub - #10

Merged
macjayz merged 1 commit into
mainfrom
feat/airnode-attested-verification
Oct 31, 2026
Merged

macjayz merged 1 commit into
mainfrom
feat/airnode-attested-verification

Conversation

@macjayz

@macjayz macjayz commented Oct 31, 2026

Copy link
Copy Markdown
Owner

What

Adds AirnodeHub's dRPC listing as an attesting peer in cross-provider verification (D2). Every answer it gives carries an EIP-191 signature, so a disagreement incident comes with evidence that anyone can re-verify offline, without trusting Heightproof.

verify: {
  endpoints: [airnodeDrpcEndpoint({ network: "base", fetchImpl: payingFetch })]
}

How it works

  • SDK (@heightproof/web3): reads responses as text, so the signed bytes survive. It checks the signature against the exact bytes received (signatureValid), and checks that the signed request hash matches the one derived from the exact request sent (requestBound), so an answer to a different request can't be replayed. Each endpoint can take its own fetchImpl, for example one wrapped with @x402/fetch to pay the $0.01-per-call charge.
  • Worker (the trust boundary): re-recovers the signer from the kept body and confirms the body holds the very result whose hash was compared. It never trusts the SDK's verdict.
  • D2: the reason cites the verified signer. A signature is attribution, not truth, so it changes no weight.
  • Storage: migration 003 adds evm_attestation (jsonb, including the signed body, capped at 64 KiB) and evm_attested_by, which is set only after the worker's re-check passes.
  • Dashboard: the request explorer shows the verified signer.
  • Refusals: a non-JSON-RPC HTTP refusal, such as an unpaid 402, is now recorded as a failed observation instead of being parsed as an answer.

shared uses @noble/curves and @noble/hashes for keccak and secp256k1 recovery. It does not depend on viem.

Testing

  • Fixtures: unit tests use a real signed response captured from the live dRPC Airnode. They cover request-hash derivation, signer recovery, a tampered body, a re-serialized body, a replayed request hash and malformed headers.
  • SDK tests: a stand-in Airnode signs with a fresh key, covering a valid and bound answer, a wrong request hash, an unpaid 402, and per-endpoint fetch routing.
  • Worker tests: they overrule a forged SDK verdict and a verdict that has no body to check.
  • Integration (real Postgres and Redis): a real signature goes through stream → worker → Postgres, and the D2 reason names the signer.
  • Live: an unpaid eth_getBalance against the real Airnode is recorded as a failed observation.
  • npm run build, typecheck, lint and test all pass, and test:integration passes with 14 tests.

Not yet exercised: a paid call end to end. It needs a wallet funded with aUSD on Base Sepolia.

Docs: docs/airnode.md

🤖 Generated with Claude Code

Adds AirnodeHub's dRPC listing as an attesting peer in D2 verification.
Every answer it gives carries an EIP-191 signature, which Heightproof
checks against the exact request sent (request hash) and the exact bytes
received.

- shared: attestation reading, passthrough request-hash derivation,
  signer recovery, and worker-side re-verification (noble, no viem dep)
- web3: airnodeDrpcEndpoint(), per-endpoint fetchImpl for x402 payment,
  responses read as text so signatures survive; non-JSON-RPC HTTP errors
  (an unpaid 402) become failed observations
- worker: re-checks every signature against the kept body and the
  compared result hash before storing; D2 reasons cite verified signers
- migration 003: evm_attestation (jsonb), evm_attested_by
- dashboard: verified signer in the request explorer
- docs/airnode.md, README, detectors, CHANGELOG

A signature is attribution, not truth: it changes no detector weight.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@macjayz
macjayz merged commit 539542f into main Oct 31, 2026
8 checks passed
@macjayz
macjayz deleted the feat/airnode-attested-verification branch October 31, 2026 21:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant