{"id":49301126,"url":"https://github.com/andreogle/airnode-attestor","last_synced_at":"2026-04-26T07:01:47.802Z","repository":{"id":349439953,"uuid":"1202265740","full_name":"andreogle/airnode-attestor","owner":"andreogle","description":null,"archived":false,"fork":false,"pushed_at":"2026-04-05T23:36:06.000Z","size":120,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-06T01:16:52.142Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/andreogle.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-05T20:19:53.000Z","updated_at":"2026-04-05T23:36:12.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/andreogle/airnode-attestor","commit_stats":null,"previous_names":["andreogle/airnode-attestor"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/andreogle/airnode-attestor","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreogle%2Fairnode-attestor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreogle%2Fairnode-attestor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreogle%2Fairnode-attestor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreogle%2Fairnode-attestor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/andreogle","download_url":"https://codeload.github.com/andreogle/airnode-attestor/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreogle%2Fairnode-attestor/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32288653,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-26T06:26:00.361Z","status":"ssl_error","status_checked_at":"2026-04-26T06:25:58.791Z","response_time":129,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-26T07:01:45.579Z","updated_at":"2026-04-26T07:01:47.792Z","avatar_url":"https://github.com/andreogle.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# airnode-attestor\n\nHTTP service that generates TLS proofs for API responses using [Reclaim Protocol](https://reclaimprotocol.org/). It sits between [Airnode](https://github.com/api3dao/airnode) and a Reclaim attestor, providing cryptographic proof that API data is authentic.\n\n## Table of contents\n\n- [Why](#why)\n- [How it works](#how-it-works)\n- [Prerequisites](#prerequisites)\n- [Quick start](#quick-start)\n- [API](#api)\n- [Configuration](#configuration)\n- [Development](#development)\n- [Proof verification](#proof-verification)\n- [License](#license)\n\n## Why\n\nAirnode serves data from off-chain APIs, but consumers have no way to verify that the data actually came from the claimed source. An operator could modify responses before signing them and nobody would know.\n\nThis service closes that gap. By routing API calls through a Reclaim attestor, it produces a TLS proof that the response is authentic — cryptographic evidence that the data came directly from the upstream API, unmodified. Combined with Airnode's existing EIP-191 signature, consumers get both operator accountability and data provenance.\n\n## How it works\n\n1. Airnode sends a `POST /v1/prove` request with the upstream URL, HTTP method, headers, and response extraction config.\n2. This service generates a throwaway Ethereum key pair for the request and opens a WebSocket tunnel to a Reclaim attestor.\n3. The attestor establishes a TLS connection to the upstream API. Secret headers (e.g., API keys) are protected using TLS 1.3 KeyUpdate — the attestor never sees them in plaintext.\n4. The upstream API response is captured. If `responseMatches` are provided, the response is validated against regex patterns and named capture groups extract values into the proof context.\n5. Zero-knowledge proofs are generated (using [gnark](https://github.com/Consensys/gnark) by default) proving the response is authentic without revealing any redacted data.\n6. The attestor verifies the proofs and signs the claim. The signature covers a keccak256 hash of the provider, parameters, and context — binding the proof to the exact request and response.\n7. The signed claim is returned to Airnode, which attaches it to the API response alongside its own EIP-191 signature.\n\n```\nAirnode ──POST /prove──\u003e airnode-attestor ──WebSocket──\u003e Reclaim Attestor ──TLS──\u003e Upstream API\n```\n\n## Prerequisites\n\n- Node.js 22+\n- Docker (for running the Reclaim attestor)\n\n## Quick start\n\n```bash\nnpm run setup          # install deps, build native modules, download ZK circuits\ncp .env.example .env   # fill in PRIVATE_KEY\nnpm run docker:up      # start attestor + gateway\n```\n\nThe gateway is available at `http://localhost:5177`.\n\n## API\n\n### `POST /v1/prove`\n\nGenerate a TLS proof for an API response.\n\n```bash\ncurl -X POST http://localhost:5177/v1/prove \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n    \"url\": \"https://api.coingecko.com/api/v3/simple/price?ids=ethereum\u0026vs_currencies=usd\",\n    \"method\": \"GET\",\n    \"responseMatches\": [\n      { \"type\": \"regex\", \"value\": \"\\\"usd\\\":\\\\s*(?\u003cprice\u003e[\\\\d.]+)\" }\n    ]\n  }'\n```\n\n**Request body:**\n\n| Field                | Type     | Required | Description                                            |\n| -------------------- | -------- | -------- | ------------------------------------------------------ |\n| `url`                | `string` | Yes      | Upstream API URL (must be http/https, no private IPs)  |\n| `method`             | `string` | Yes      | HTTP method (`GET`, `POST`, `PUT`, `PATCH`)            |\n| `headers`            | `object` | No       | Secret headers (redacted from proof via TLS KeyUpdate) |\n| `body`               | `string` | No       | Request body to send to the upstream API               |\n| `responseMatches`    | `array`  | Yes      | Regex patterns the response must match (min 1)         |\n| `responseRedactions` | `array`  | No       | JSON paths to selectively reveal to the attestor       |\n\n**Response (200):**\n\n```json\n{\n  \"claim\": {\n    \"provider\": \"http\",\n    \"parameters\": \"{\\\"url\\\":\\\"...\\\",\\\"method\\\":\\\"GET\\\",\\\"responseMatches\\\":[...]}\",\n    \"context\": \"{\\\"extractedParameters\\\":{\\\"price\\\":\\\"2064.01\\\"}}\",\n    \"owner\": \"0x...\",\n    \"timestampS\": 1775345832,\n    \"epoch\": 1,\n    \"identifier\": \"0x...\"\n  },\n  \"signatures\": {\n    \"attestorAddress\": \"0x...\",\n    \"claimSignature\": \"0x...\"\n  }\n}\n```\n\n### `GET /v1/health`\n\nReturns service status and attestor URL.\n\n## Configuration\n\n| Variable           | Default                  | Description                           |\n| ------------------ | ------------------------ | ------------------------------------- |\n| `PORT`             | `5177`                   | HTTP server port                      |\n| `ATTESTOR_URL`     | `ws://localhost:8001/ws` | WebSocket URL of the Reclaim attestor |\n| `ZK_ENGINE`        | `gnark`                  | ZK engine: `gnark`, `snarkjs`, `stwo` |\n| `PROVE_TIMEOUT_MS` | `30000`                  | Proof generation timeout (ms)         |\n\n## Development\n\n```bash\nnpm run setup        # install deps + build native modules + download ZK circuits\nnpm run dev          # start with hot reload (requires a running attestor)\nnpm test             # run tests\nnpm run typecheck    # type check without emitting\nnpm run lint         # check formatting and linting\nnpm run fmt          # auto-fix formatting and linting\n```\n\nDocker commands:\n\n```bash\nnpm run docker:up    # build gateway + pull attestor, start both\nnpm run docker:down  # tear down\n```\n\n## Proof verification\n\nProofs can be verified off-chain using `viem`:\n\n```typescript\nimport { keccak256, toHex, recoverAddress, hashMessage } from 'viem';\n\n// Verify the claim identifier\nconst computed = keccak256(toHex(claim.provider + '\\n' + claim.parameters + '\\n' + claim.context));\nassert(computed === claim.identifier);\n\n// Verify the attestor signature\nconst signData = [claim.identifier, claim.owner, String(claim.timestampS), String(claim.epoch)].join('\\n');\nconst recovered = await recoverAddress({\n  hash: hashMessage(signData),\n  signature: claimSignature,\n});\nassert(recovered === signatures.attestorAddress);\n```\n\nOn-chain verification is available via [`@reclaimprotocol/reclaim-solidity-sdk`](https://github.com/reclaimprotocol/reclaim-solidity-sdk).\n\n## License\n\nThis project is licensed under the [GNU Affero General Public License v3.0](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandreogle%2Fairnode-attestor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandreogle%2Fairnode-attestor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandreogle%2Fairnode-attestor/lists"}