fix(blockchain): correct EAS schema UID derivation and harden event decoding#50
Conversation
…ecoding
The EAS SchemaRegistry derives schema UIDs as
keccak256(abi.encodePacked(schema, resolver, revocable)). The previous
fallback in _ensure_schema_registered hashed only the schema string, which
produces a completely different UID than the one the on-chain contract
assigns. Any attestation using that fallback UID would fail verification
against the real schema, breaking the entire anchoring flow whenever log
parsing failed (for example, if a node reorders receipt logs or the first
log turns out to belong to a different contract).
This commit:
1. Adds BlockchainService.compute_schema_uid(schema, resolver, revocable),
a static method that mirrors SchemaRegistry._getUID exactly. The method
uses eth_abi.packed.encode_packed and Web3.keccak to produce the same
32-byte UID the on-chain registry would emit.
2. Rewrites _ensure_schema_registered to:
- Derive the canonical UID offline before spending any gas.
- Short-circuit via SchemaRegistry.getSchema(uid) when the schema is
already registered, avoiding redundant transactions.
- Scan every log (not just receipt.logs[0]) for a non-zero indexed uid
in topics[1] after registration.
- Fall back to the canonical UID (never keccak(schema) alone) if log
parsing returns nothing, since the canonical UID is provably equal to
the on-chain value by construction.
3. Replaces the brittle byte-slicing Attested event parser in
anchor_artifact with _extract_attestation_uid, which uses
contract.events.Attested().process_log(log) for ABI-correct decoding.
The helper also has a topic-signature fallback so it still works if
other contracts emit logs in the same transaction. Previously the code
assumed the first log was always Attested and that its data started
with the uid, which is not guaranteed by the EAS interface.
4. Adds Attested / Revoked / Registered event fragments to blockchain/abi.py
so web3.py can decode them, and pins canonical schema-UID placeholder
constants (ATTESTIX_SCHEMA_UID_BASE_SEPOLIA / _MAINNET) for the
follow-up registration SOP to populate.
Tests (tests/unit/test_blockchain.py) verify:
- compute_schema_uid matches keccak(encode_packed(...)) for the Attestix
schema and for a known EAS sample schema (vote).
- UID changes when resolver or revocable flag changes.
- Canonical UID differs from keccak(schema) alone (guards the regression).
- _extract_attestation_uid decodes a synthesized Attested log and ignores
unrelated logs in the same receipt.
20 tests pass (9 new + 11 existing); full unit suite 193 pass / 1 skip.
Refs: https://github.com/ethereum-attestation-service/eas-contracts/blob/master/contracts/SchemaRegistry.sol
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 14 minutes and 47 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (3)
Note
|
Minor version bump (0.2.5 -> 0.3.0) bundling seven previously merged but unreleased pull requests. Security: - CRITICAL: delegation chain auth bypass fix (PR #45). Parent tokens and capability attenuation are now strictly verified on every delegation verify. - SSRF hardening on agent discovery, DID resolution, credential fetch (PR #47). - Timing-safe comparisons for signature and token equality checks (PR #47). - Seven REST API router exception paths no longer leak internals to clients (PR #47). Added: - Real LangChain integration using BaseCallbackHandler (PR #42). - Real OpenAI Agents SDK integration via MCPServerStdio (PR #48). - Real CrewAI integration via MCPServerAdapter (PR #51). Fixed: - Article 43 Annex III conformity assessment now correctly differentiates categories that require a notified body versus permitted self-assessment (PR #46). - EAS schema UID derivation matches the on-chain EAS encoding (PR #50). - Attested event decoding prefers web3.py ABI decoding with a hardened topic-signature fallback (PR #50). - tests/conftest.py blockchain_service_mock now emits an Attested log with the correct topic[0] so the hardened decoder is exercised end to end. Infrastructure: - GitHub Actions CI/CD: pytest matrix (py 3.10 through 3.13), ruff, mypy, bandit, pip-audit, plus a PyPI publish workflow on release creation (PR #49). - Default pytest addopts now include "-p no:logfire" so that transitive installs of opentelemetry-sdk (via CrewAI) do not abort collection with ImportError: cannot import name 'ReadableLogRecord'. Files updated: pyproject.toml (0.2.5 -> 0.3.0, addopts), attestix/__init__.py (0.2.4 -> 0.3.0), server.json (0.2.4 -> 0.3.0), CHANGELOG.md (0.3.0 section), tests/conftest.py (Attested log mock). All 358 tests pass, 1 skipped. twine check on wheel and sdist: PASSED.
…real integrations) Sync public docs and pyproject classifiers to the v0.3.0 ground truth after all today's merges (PRs #41, #42, #45, #46, #47, #48, #49, #50, #51, #55). README.md: - Update hero subtitle to 358 tests, 44 REST endpoints, and the 3 real framework integrations (LangChain, OpenAI Agents SDK, CrewAI). - Replace the 284 / 91 legacy test counts with the current split (358 passing, 1 skipped on Windows for POSIX chmod; 79 benchmarks). docs/changelog.md: - Add a full v0.3.0 section covering: real LangChain, OpenAI Agents, and CrewAI integrations; 7 framework examples + 15 integration tests; GitHub Actions CI/CD matrix across Python 3.10-3.13; EAS schema UID correctness; Annex III Article 43 differentiation; the CRITICAL delegation chain auth bypass fix; the security batch (SSRF, API timing, exception leaks, display_name, key perms); the 4 HIGH blockers fix + PyJWT CVE-2026-32597 mitigation + dependency pinning; and the test count move from 284 to 358. docs/roadmap.md: - Add Phase 3.5 (Complete, v0.3.0) describing the real framework integrations, CI/CD, and security hardening delivered today. - Note EAS integration is complete and testnet-ready on Base Sepolia but mainnet schema registration is still outstanding. - Rewrite the version plan: 0.3.0 is Phase 3.5 (shipped), 0.4.0 now targets broader GDPR coverage, ISO/IEC 42001 alignment, and EAS mainnet schema registration. ERC-8004 / A2A sync / ANS / Polygon ID move to 0.5.0. pyproject.toml: - Drop the Python 3.10 classifier so classifiers match the CI test matrix (3.11, 3.12, 3.13). requires-python remains >=3.10 for now so existing installs do not break. Out of tree (gitignored) and updated separately: - paper/internal/standards-coverage.md: reconciled test totals to 358, flagged DPDPA as NOT IMPLEMENTED (pitch reference only, no code), scoped broader GDPR beyond Article 17 to v0.4.0, and noted EAS mainnet schema is not yet registered. - MEMORY.md (global): updated version to 0.3.0, test count to 358, services to 12, REST endpoints to 44, GitHub stars to 15, PyPI totals 2,947 / 941 month, and documented the 10 merged PRs and the real-vs-simulated integration split.
…real integrations) (#58) Sync public docs and pyproject classifiers to the v0.3.0 ground truth after all today's merges (PRs #41, #42, #45, #46, #47, #48, #49, #50, #51, #55). README.md: - Update hero subtitle to 358 tests, 44 REST endpoints, and the 3 real framework integrations (LangChain, OpenAI Agents SDK, CrewAI). - Replace the 284 / 91 legacy test counts with the current split (358 passing, 1 skipped on Windows for POSIX chmod; 79 benchmarks). docs/changelog.md: - Add a full v0.3.0 section covering: real LangChain, OpenAI Agents, and CrewAI integrations; 7 framework examples + 15 integration tests; GitHub Actions CI/CD matrix across Python 3.10-3.13; EAS schema UID correctness; Annex III Article 43 differentiation; the CRITICAL delegation chain auth bypass fix; the security batch (SSRF, API timing, exception leaks, display_name, key perms); the 4 HIGH blockers fix + PyJWT CVE-2026-32597 mitigation + dependency pinning; and the test count move from 284 to 358. docs/roadmap.md: - Add Phase 3.5 (Complete, v0.3.0) describing the real framework integrations, CI/CD, and security hardening delivered today. - Note EAS integration is complete and testnet-ready on Base Sepolia but mainnet schema registration is still outstanding. - Rewrite the version plan: 0.3.0 is Phase 3.5 (shipped), 0.4.0 now targets broader GDPR coverage, ISO/IEC 42001 alignment, and EAS mainnet schema registration. ERC-8004 / A2A sync / ANS / Polygon ID move to 0.5.0. pyproject.toml: - Drop the Python 3.10 classifier so classifiers match the CI test matrix (3.11, 3.12, 3.13). requires-python remains >=3.10 for now so existing installs do not break. Out of tree (gitignored) and updated separately: - paper/internal/standards-coverage.md: reconciled test totals to 358, flagged DPDPA as NOT IMPLEMENTED (pitch reference only, no code), scoped broader GDPR beyond Article 17 to v0.4.0, and noted EAS mainnet schema is not yet registered. - MEMORY.md (global): updated version to 0.3.0, test count to 358, services to 12, REST endpoints to 44, GitHub stars to 15, PyPI totals 2,947 / 941 month, and documented the 10 merged PRs and the real-vs-simulated integration split.
…rce Cloudflare redeploy The Cloudflare Pages project is still wired to this monorepo's website/ subdirectory, not the split-out VibeTensor/attestix-website repo. When PR #1 and PR #2 merged in attestix-website, the live site never picked them up because Cloudflare was watching this directory instead. This commit replays both PRs against website/ so the live site actually updates before tomorrow's YC demo. Content fixes replayed from attestix-website#1 Replace 16-reps Julie Zhuo testimonial loop with three distinct real validation quotes (Yoshua Bengio, Alvaro Cabrejas Egea from the EU AI Office, Matt Pagett). Drop the second Marquee and lower repeat from 4 to 2 so the same quote does not surface 16 times in the DOM. Add VHS demo tape and asciinema walkthrough files under public/. Add a redirect stub at content/docs/guides/eu-compliance-walkthrough.mdx so the old link Matt Pagett hit no longer 404s. Content fixes replayed from attestix-website#2 Fix NumberTicker SSR so initial render shows the target value formatted with Intl.NumberFormat instead of the start value. Crawlers, no-JS visitors, and social previews now see 47 / 358 / 6 even if hydration never completes. On the client the component snaps to startValue on first-in-view and springs up to value, preserving the count-up effect. Add v0.3.0 flagship story to the landing page. Technology Stack gains a new Agent Frameworks category with LangChain, OpenAI Agents SDK, and CrewAI, labelled as real shipped integrations (not simulations). Hero subcopy and FAQ copy updated to mention the three integrations. Bump ATTESTIX_VERSION default and package.json version from 0.2.4 to 0.3.0. Replace stale 284 test counts with 358 (1 skipped on Windows) across the architecture guide, configuration reference, contributing guide, roadmap version plan, and research abstract. Historical 0.2.0 and 0.2.1 changelog entries keep their original 284 figure but now point forward to the 0.3.0 entry. Add a full 0.3.0 changelog entry covering every PR merged today: framework integrations (#41, #42, #48, #51), CI/CD setup (#49, #54, #56, #58), six security fixes (#45, #47, #55), EAS schema UID and Article 43 correctness fixes (#46, #50), and persona e2e polish (#57). Rewrite the sitemap generator to import the Fumadocs source loader and emit every docs page in content/docs/** automatically, weighted per section. The previous hand-maintained list surfaced only 4 of 17 docs pages to crawlers. The final sitemap now has 32 URLs (was 14).
…rce Cloudflare redeploy (#60) The Cloudflare Pages project is still wired to this monorepo's website/ subdirectory, not the split-out VibeTensor/attestix-website repo. When PR #1 and PR #2 merged in attestix-website, the live site never picked them up because Cloudflare was watching this directory instead. This commit replays both PRs against website/ so the live site actually updates before tomorrow's YC demo. Content fixes replayed from attestix-website#1 Replace 16-reps Julie Zhuo testimonial loop with three distinct real validation quotes (Yoshua Bengio, Alvaro Cabrejas Egea from the EU AI Office, Matt Pagett). Drop the second Marquee and lower repeat from 4 to 2 so the same quote does not surface 16 times in the DOM. Add VHS demo tape and asciinema walkthrough files under public/. Add a redirect stub at content/docs/guides/eu-compliance-walkthrough.mdx so the old link Matt Pagett hit no longer 404s. Content fixes replayed from attestix-website#2 Fix NumberTicker SSR so initial render shows the target value formatted with Intl.NumberFormat instead of the start value. Crawlers, no-JS visitors, and social previews now see 47 / 358 / 6 even if hydration never completes. On the client the component snaps to startValue on first-in-view and springs up to value, preserving the count-up effect. Add v0.3.0 flagship story to the landing page. Technology Stack gains a new Agent Frameworks category with LangChain, OpenAI Agents SDK, and CrewAI, labelled as real shipped integrations (not simulations). Hero subcopy and FAQ copy updated to mention the three integrations. Bump ATTESTIX_VERSION default and package.json version from 0.2.4 to 0.3.0. Replace stale 284 test counts with 358 (1 skipped on Windows) across the architecture guide, configuration reference, contributing guide, roadmap version plan, and research abstract. Historical 0.2.0 and 0.2.1 changelog entries keep their original 284 figure but now point forward to the 0.3.0 entry. Add a full 0.3.0 changelog entry covering every PR merged today: framework integrations (#41, #42, #48, #51), CI/CD setup (#49, #54, #56, #58), six security fixes (#45, #47, #55), EAS schema UID and Article 43 correctness fixes (#46, #50), and persona e2e polish (#57). Rewrite the sitemap generator to import the Fumadocs source loader and emit every docs page in content/docs/** automatically, weighted per section. The previous hand-maintained list surfaced only 4 of 17 docs pages to crawlers. The final sitemap now has 32 URLs (was 14).
Summary
_ensure_schema_registeredpreviously fell back tokeccak(schema_text)when log parsing failed, which produces a completely different UID from the canonical on-chain value. Replaced withkeccak256(abi.encodePacked(schema, resolver, revocable))(the EASSchemaRegistry._getUIDformula) via a newBlockchainService.compute_schema_uid()static method. Also added an offlinegetSchema(uid)short-circuit so we never spend gas when the schema is already registered.log.data[:32]slice with_extract_attestation_uid(), which usescontract.events.Attested().process_log(log)for ABI-correct decoding and falls back to a topic-signature match when needed. AddedAttested/Revoked/Registeredevent fragments toblockchain/abi.pyso web3.py can decode them.ATTESTIX_SCHEMA_UID_BASE_SEPOLIA/_MAINNETplaceholders toblockchain/abi.py, ready to be populated once the schema is registered per the SOP.Reference: https://github.com/ethereum-attestation-service/eas-contracts/blob/master/contracts/SchemaRegistry.sol
Why this matters
Every Attestix anchor embeds a schema UID. If the fallback UID does not match the on-chain schema, every subsequent
verify_anchorcall fails becausegetAttestation(uid)returns a record whoseschemafield does not match the cached UID. The fallback only triggered on receipt-log parsing edge cases, but when it did trigger the whole anchoring system silently broke. The new code derives the canonical UID offline and uses it as the fallback, so the fallback is by construction correct.Test plan
python -m pytest tests/unit/test_blockchain.py -p no:logfire-> 20 passedpython -m pytest tests/unit/ -p no:logfire-> 193 passed, 1 skipped, 0 failedTestSchemaUIDDerivationclass verifies canonical UID matcheskeccak(encode_packed(...))for the Attestix schema and a known EAS sample schema, and that the canonical UID differs fromkeccak(schema)alone (regression guard).TestAttestedEventDecodingclass synthesizes anAttestedlog with the proper topic signature and confirms_extract_attestation_uiddecodes the uid and ignores unrelated logs.SOP
Step-by-step registration procedure documented in
paper/internal/runbooks/eas-schema-registration-sop.md(gitignored, not part of this PR). Contents: canonical schema text, UID derivation, contract addresses, Base Sepolia faucets, cost estimate, and rollback notes. Mainnet registration requires explicit owner confirmation.