From 24437deb7822a5c95c3d7ba366c7ae5625958d1c Mon Sep 17 00:00:00 2001 From: devin distefano Date: Wed, 20 May 2026 18:55:37 -0500 Subject: [PATCH 1/9] initial pass --- src/config/sidebar.ts | 8 + .../generating-reports-single-values.mdx | 12 +- .../generating-reports-structs.mdx | 4 +- .../onchain-write/overview-go.mdx | 11 + .../onchain-write/overview-ts.mdx | 11 + .../submitting-reports-onchain.mdx | 7 +- .../workflow/using-http-client/index.mdx | 31 +- .../submitting-reports-http-go.mdx | 30 +- .../submitting-reports-http-ts.mdx | 30 +- .../verifying-reports-offchain-go.mdx | 300 +++++++++++ .../verifying-reports-offchain-ts.mdx | 257 +++++++++ src/content/cre/llms-full-go.txt | 488 +++++++++++++++++- src/content/cre/llms-full-ts.txt | 401 +++++++++++++- src/content/cre/reference/sdk/core-go.mdx | 81 ++- src/content/cre/reference/sdk/core-ts.mdx | 72 ++- .../cre/reference/sdk/http-client-go.mdx | 19 +- .../cre/reference/sdk/http-client-ts.mdx | 7 +- src/content/cre/reference/sdk/overview-go.mdx | 2 +- src/content/cre/reference/sdk/overview-ts.mdx | 2 +- 19 files changed, 1708 insertions(+), 65 deletions(-) create mode 100644 src/content/cre/guides/workflow/using-http-client/verifying-reports-offchain-go.mdx create mode 100644 src/content/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts.mdx diff --git a/src/config/sidebar.ts b/src/config/sidebar.ts index b541d804798..75f13155597 100644 --- a/src/config/sidebar.ts +++ b/src/config/sidebar.ts @@ -387,6 +387,14 @@ export const SIDEBAR: Partial> = { "cre/guides/workflow/using-http-client/submitting-reports-http-go", ], }, + { + title: "Verifying CRE Reports Offchain", + url: "cre/guides/workflow/using-http-client/verifying-reports-offchain", + highlightAsCurrent: [ + "cre/guides/workflow/using-http-client/verifying-reports-offchain-ts", + "cre/guides/workflow/using-http-client/verifying-reports-offchain-go", + ], + }, ], }, { diff --git a/src/content/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values.mdx b/src/content/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values.mdx index ca4ca5793d8..a505db987cd 100644 --- a/src/content/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values.mdx +++ b/src/content/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values.mdx @@ -11,7 +11,9 @@ metadata: import { Aside } from "@components" -This guide shows how to manually generate a report containing a single value (like `uint256`, `address`, or `bool`). This is useful when you need to send a simple value onchain but don't have a struct or binding helper available. +This guide shows how to manually generate a **CRE report** containing a single value (like `uint256`, `address`, or `bool`). A CRE report is a DON-signed package from `runtime.GenerateReport()` / `runtime.report()`—your encoded data plus workflow metadata and signatures. It is **not** a [Data Streams](/data-streams) report. + +This guide covers **creating** the report (step 2 in the sender flow). **Delivering** it is a separate step—see the table below. **Use this approach when:** @@ -31,10 +33,12 @@ Manually generating a report for a single value involves two main steps: 1. **ABI-encode the value** into bytes using the `go-ethereum/accounts/abi` package 1. **Generate a cryptographically signed report** using `runtime.GenerateReport()` -The resulting report can then be: +| After `GenerateReport()` / `report()` | Guide | Who verifies? | +| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- | +| `evm.Client.WriteReport()` / `writeReport()` | [Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain) | `KeystoneForwarder` onchain | +| `http.Client` `SendReport()` | [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http) | Receiver: [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain) or your API | -- Submitted to the blockchain via `evm.Client.WriteReport()` (see [Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain)) -- Sent to an HTTP endpoint via `http.Client` (see [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http)) +See [API Interactions — CRE reports over HTTP](/cre/guides/workflow/using-http-client#cre-reports-over-http) for the sender → receiver mental model. +## Where this guide fits + +| Question | Answer | +| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| What is the report? | Output of `runtime.report()` after your workflow DON reaches consensus: encoded payload + metadata + signatures. Not a Data Streams report. | +| Where does it come from? | **Inside this workflow**—after your logic runs (fetch data, compute, encode). There is no separate "get report" step. | +| What does this guide cover? | Steps 2–3: generate the report, then `sendReport()` to your API. | +| Who verifies it? | The **receiver**—your HTTP service or a separate CRE workflow. See [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts). | + +**Sender flow in one workflow execution:** + +1. Trigger fires (cron, HTTP, …). +2. Your callback runs (API calls, encoding, etc.). +3. `runtime.report()` — DON produces a signed `ReportResponse`. +4. `sendReport()` — format and POST to your URL. + +For a conceptual overview and diagram, see [API Interactions — CRE reports over HTTP](/cre/guides/workflow/using-http-client#cre-reports-over-http). + ## Prerequisites - Familiarity with [making POST requests](/cre/guides/workflow/using-http-client/post-request) @@ -560,15 +578,13 @@ const onCronTrigger = (runtime: Runtime): MyResult => { 1. **Always use `cacheSettings`**: Include caching in every transformation function to prevent worst-case duplicate submission scenarios 1. **Implement API-side deduplication**: Your receiving API must implement deduplication using the **hash of the report** (`keccak256(rawReport)`) to detect and reject duplicate submissions -1. **Verify signatures before processing**: Your API must verify the cryptographic signatures against DON public keys before trusting report data (see note below about signature verification) +1. **Verify on the receiver**: The sender does not validate the report—your API or a [receiver CRE workflow](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts) must verify before trusting payload data 1. **Match your API's format exactly**: Study your API's documentation to understand the expected format (binary, JSON, headers, etc.) 1. **Handle errors gracefully**: Check HTTP status codes and provide meaningful error messages {/* prettier-ignore */} ## Troubleshooting @@ -586,6 +602,8 @@ const onCronTrigger = (runtime: Runtime): MyResult => { ## Learn more +- **[API Interactions — CRE reports over HTTP](/cre/guides/workflow/using-http-client#cre-reports-over-http)** — Sender → receiver overview +- **[Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts)** — Receiver workflow: verify before trusting payload - **[HTTP Client SDK Reference](/cre/reference/sdk/http-client-ts)** — Complete API reference including `sendReport()` and `ReportResponse` - **[POST Requests](/cre/guides/workflow/using-http-client/post-request)** — Learn about HTTP request patterns and caching - **[Writing Data Onchain](/cre/guides/workflow/using-evm-client/onchain-write/writing-data-onchain)** — Detailed guide on encoding single values, structs, and complex types using Viem diff --git a/src/content/cre/guides/workflow/using-http-client/verifying-reports-offchain-go.mdx b/src/content/cre/guides/workflow/using-http-client/verifying-reports-offchain-go.mdx new file mode 100644 index 00000000000..24e90663a0f --- /dev/null +++ b/src/content/cre/guides/workflow/using-http-client/verifying-reports-offchain-go.mdx @@ -0,0 +1,300 @@ +--- +section: cre +title: "Verifying CRE Reports Offchain" +date: Last Modified +sdkLang: "go" +pageId: "guides-workflow-http-verify-reports-offchain" +metadata: + description: "Verify CRE report signatures offchain in Go: parse reports, validate DON signatures against the onchain registry, and read workflow metadata." + datePublished: "2026-05-20" + lastModified: "2026-05-20" +--- + +import { Aside } from "@components" + +This guide is for the **receiver** side: you already received a CRE report package (usually via HTTP) and need to **prove it is authentic** before using the payload. + +When a workflow delivers results via HTTP (or another offchain channel), nothing onchain automatically validates the report. **You must verify signatures before trusting the data.** + +The CRE SDK provides `cre.ParseReport()` to do this inside a workflow. Verification runs offchain in your callback: signatures are checked with local cryptography, while authorized signer addresses are loaded via **read-only calls to the onchain Capability Registry** (default: Ethereum Mainnet). Results are cached per DON. + +{/* prettier-ignore */} + + +{/* prettier-ignore */} + + +## Where this guide fits + +| Question | Answer | +| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| What is the report? | Same CRE report the **sender** created with `runtime.GenerateReport()`—not a Data Streams report. See [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-go#where-this-guide-fits). | +| Where does it come from? | Another workflow (or system) already ran sender steps: logic → `GenerateReport()` → HTTP POST. You receive `rawReport`, `context`, and `signatures` in the request body. | +| What does this guide cover? | Step 4: `cre.ParseReport()` before you use `Body()` or take side effects. | +| Same workflow as the sender? | Often **no**—common pattern is Workflow A (publish) and Workflow B (ingest with HTTP trigger). | + +**Receiver flow:** + +1. HTTP trigger (or your API) receives the POST payload. +2. Decode hex fields into bytes. +3. `cre.ParseReport()` — verify signatures and read metadata. +4. Use trusted `Body()` in your logic. + +For the full sender → receiver diagram, see [API Interactions — CRE reports over HTTP](/cre/guides/workflow/using-http-client#cre-reports-over-http). + +## What you'll learn + +- When to verify reports offchain vs relying on onchain forwarders +- How `cre.ParseReport()` validates signatures and reads metadata +- How to build a receiver workflow that accepts reports over HTTP +- How to restrict verification to specific CRE environments or zones + +## Prerequisites + +- **SDK**: `cre-sdk-go` v1.8.0 or later (report verification support) +- Familiarity with [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-go) (report structure and JSON payload patterns) +- For HTTP-triggered receivers: [HTTP Trigger configuration](/cre/guides/workflow/using-triggers/http-trigger/configuration-go) + +## Onchain vs offchain verification + +| Aspect | Offchain (`cre.ParseReport`) | Onchain (`KeystoneForwarder`) | +| -------------------- | ------------------------------------------------------------ | --------------------------------- | +| **Where it runs** | Inside your CRE workflow callback | In a smart contract transaction | +| **Signature check** | Local `ecrecover` on report hash | Contract logic onchain | +| **Signer allowlist** | Read from Capability Registry (`getDON`, `getNodesByP2PIds`) | Forwarder + registry | +| **Typical use** | HTTP APIs, webhooks, ingest workflows | Consumer contracts via `onReport` | + +Offchain verification still uses **onchain data as a trust anchor**: the first time a DON is seen, the SDK reads the production Capability Registry on Ethereum Mainnet to learn `f` and authorized signer addresses. + +Default (`cre.ProductionEnvironment()`): + +- **Chain**: Ethereum Mainnet (chain selector `5009297550715157269`) +- **Registry**: `0x76c9cf548b4179F8901cda1f8623568b58215E62` + +## How verification works + +1. **Parse the report header** from `rawReport` (109-byte metadata + body). +2. **Fetch DON info** from the registry (if not cached): fault tolerance `f` and signer addresses. +3. **Verify signatures**: compute `keccak256(keccak256(rawReport) || reportContext)`, recover signers, require **f+1** valid signatures from authorized nodes. +4. **Return a `*cre.Report`** with accessors for workflow ID, owner, execution ID, body, and more. + +If verification fails, `cre.ParseReport()` returns an error (for example, `ErrUnknownSigner`, `ErrWrongSignatureCount`, or registry read failure). + +## Complete example: HTTP receiver workflow + +This workflow accepts a JSON payload (matching the format from [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-go#pattern-4-json-formatted-report)), verifies it, then processes the trusted body. + +```go +package main + +import ( + "encoding/hex" + "encoding/json" + "log/slog" + + "github.com/smartcontractkit/cre-sdk-go/capabilities/networking/http" + "github.com/smartcontractkit/cre-sdk-go/cre" +) + +type Config struct { + AuthorizedKey string `json:"authorized_key"` +} + +func InitWorkflow(cfg *Config, _ *slog.Logger, _ cre.SecretsProvider) (cre.Workflow[*Config], error) { + return cre.Workflow[*Config]{ + cre.Handler(http.Trigger(&http.Config{AuthorizedKeys: []*http.AuthorizedKey{{PublicKey: cfg.AuthorizedKey}}}), run), + }, nil +} + +type ParsedPayload struct { + Report string `json:"report"` + Context string `json:"context"` + Sigs []string `json:"signatures"` +} + +func (p *ParsedPayload) Decode() (*DecodedReport, error) { + report := &DecodedReport{} + var err error + + if report.Report, err = hex.DecodeString(p.Report); err != nil { + return nil, err + } + if report.Context, err = hex.DecodeString(p.Context); err != nil { + return nil, err + } + + report.Sigs = make([][]byte, len(p.Sigs)) + for i, sigHex := range p.Sigs { + report.Sigs[i], err = hex.DecodeString(sigHex) + if err != nil { + return nil, err + } + } + + return report, nil +} + +type DecodedReport struct { + Report []byte + Context []byte + Sigs [][]byte +} + +func run(_ *Config, runtime cre.Runtime, payload *http.Payload) (bool, error) { + parsed := &ParsedPayload{} + if err := json.Unmarshal(payload.Input, parsed); err != nil { + return false, err + } + + decoded, err := parsed.Decode() + if err != nil { + return false, err + } + + report, err := cre.ParseReport(runtime, decoded.Report, decoded.Sigs, decoded.Context) + if err != nil { + return false, err + } + + runtime.Logger().Info("Verified report", + "workflowId", report.WorkflowID(), + "executionId", report.ExecutionID(), + ) + + // Use report.Body() for your application logic (ABI-encoded payload from the sender workflow) + _ = report.Body() + + return true, nil +} +``` + +**What's happening:** + +1. An external system POSTs hex-encoded `report`, `context`, and `signatures` to your HTTP trigger. +2. `cre.ParseReport()` verifies signatures against the production CRE registry. +3. On success, you read metadata and `Body()` safely. + +{/* prettier-ignore */} + + +## Report payload format + +Receivers need three fields (plus optional metadata your API may add): + +| Field | Description | +| ------------ | ------------------------------------------------------------------ | +| `report` | Hex-encoded `rawReport` bytes (metadata header + workflow payload) | +| `context` | Hex-encoded `reportContext` (config digest + sequence number) | +| `signatures` | Array of hex-encoded 65-byte ECDSA signatures from DON nodes | + +The `reportContext` layout used by the SDK: + +- Bytes 0–31: config digest +- Bytes 32–39: sequence number (big-endian `uint64`) + +## API reference + +See [SDK Reference: Core — Report verification](/cre/reference/sdk/core-go#report-verification) for full signatures, types, and errors. + +### `cre.ParseReport()` + +```go +func ParseReport(runtime Runtime, rawReport []byte, signatures [][]byte, reportContext []byte) (*Report, error) +``` + +Parses and verifies a report against the production CRE environment. Use `ParseReportWithConfig` for custom environments or zones. + +### `*cre.Report` accessors + +After a successful parse: + +| Method | Description | +| ----------------- | ----------------------------------------- | +| `WorkflowID()` | Workflow hash (`bytes32` as hex) | +| `WorkflowOwner()` | Deployer address (hex) | +| `WorkflowName()` | Workflow name field from metadata | +| `ExecutionID()` | Unique execution identifier | +| `DONID()` | DON that produced the report | +| `Timestamp()` | Report timestamp (Unix seconds) | +| `Body()` | Encoded payload after the 109-byte header | +| `SeqNr()` | Sequence number from report context | +| `ConfigDigest()` | Config digest from report context | + +### `cre.ReportParseConfig` + +```go +config := cre.ReportParseConfig{ + AcceptedZones: []cre.Zone{ + cre.ZoneFromEnvironment(cre.ProductionEnvironment(), 1), + }, + AcceptedEnvironments: []cre.Environment{cre.ProductionEnvironment()}, + SkipSignatureVerification: false, +} +report, err := cre.ParseReportWithConfig(runtime, rawReport, sigs, reportContext, config) +``` + +| Field | Description | +| --------------------------- | ---------------------------------------------------------------------------------------------------------- | +| `AcceptedEnvironments` | Registry environments to check (defaults to production) | +| `AcceptedZones` | Restrict to specific DON IDs within an environment | +| `SkipSignatureVerification` | Parse header only; call `report.VerifySignatures()` or `VerifySignaturesWithConfig()` afterward when ready | + +### Deferred verification + +If you set `SkipSignatureVerification: true` in `ParseReportWithConfig`, parse the header first (for filtering), then verify: + +```go +report, err := cre.ParseReportWithConfig(runtime, rawReport, sigs, reportContext, cre.ReportParseConfig{ + SkipSignatureVerification: true, +}) +if err != nil { + return false, err +} + +// Optional: inspect report.WorkflowID(), report.DONID(), etc. before registry reads + +if err := report.VerifySignatures(runtime); err != nil { + return false, err +} +``` + +## Best practices + +1. **Verify before side effects**: Call `cre.ParseReport()` before writing to databases, chains, or external systems. +2. **Permission on metadata**: After verification, check `WorkflowID()`, `WorkflowOwner()`, or `DONID()` match your expectations. +3. **Deduplicate by execution ID**: Use `ExecutionID()` or `keccak256(rawReport)` to reject replays (see [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-go#understanding-cachesettings-for-reports)). +4. **Do not skip signature verification in production** unless you have another trust path. + +## Troubleshooting + +**`ErrUnknownSigner`** + +- Signatures may be from a different DON or stale registry config. +- Confirm the sender workflow used production CRE and the report was not tampered with. + +**`ErrWrongSignatureCount`** + +- At least **f+1** valid signatures are required. + +**`could not read from chain ...`** + +- Registry read failed (RPC/network). Retry or check simulation vs production EVM access. + +**`ErrRawReportTooShort`** + +- `rawReport` is missing the 109-byte metadata header. + +## Learn more + +- **[API Interactions — CRE reports over HTTP](/cre/guides/workflow/using-http-client#cre-reports-over-http)** — Sender → receiver overview +- **[Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-go)** — Sender workflow: create and POST the report +- **[SDK Reference: Core — Report verification](/cre/reference/sdk/core-go#report-verification)** — `ParseReport`, `Report`, and `ReportParseConfig` +- **[HTTP Trigger Overview](/cre/guides/workflow/using-triggers/http-trigger/overview-go)** — Trigger deployed receiver workflows +- **[Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain)** — Onchain forwarder verification path +- **[Building Consumer Contracts](/cre/guides/workflow/using-evm-client/onchain-write/building-consumer-contracts)** — Permissioning `onReport` with workflow metadata diff --git a/src/content/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts.mdx b/src/content/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts.mdx new file mode 100644 index 00000000000..4aa4f3e519c --- /dev/null +++ b/src/content/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts.mdx @@ -0,0 +1,257 @@ +--- +section: cre +title: "Verifying CRE Reports Offchain" +date: Last Modified +sdkLang: "ts" +pageId: "guides-workflow-http-verify-reports-offchain" +metadata: + description: "Verify CRE report signatures offchain in TypeScript: parse reports, validate DON signatures against the onchain registry, and read workflow metadata." + datePublished: "2026-05-20" + lastModified: "2026-05-20" +--- + +import { Aside } from "@components" + +This guide is for the **receiver** side: you already received a CRE report package (usually via HTTP) and need to **prove it is authentic** before using the payload. + +When a workflow delivers results via HTTP (or another offchain channel), nothing onchain automatically validates the report. **You must verify signatures before trusting the data.** + +The CRE SDK provides `Report.parse()` to do this inside a workflow. Verification runs **offchain** in your callback: signatures are checked with local cryptography, while authorized signer addresses are loaded via **read-only calls to the onchain Capability Registry** (default: Ethereum Mainnet). Results are cached per DON. + +{/* prettier-ignore */} + + +{/* prettier-ignore */} + + +## Where this guide fits + +| Question | Answer | +| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| What is the report? | Same CRE report the **sender** created with `runtime.report()`—not a Data Streams report. See [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts#where-this-guide-fits). | +| Where does it come from? | Another workflow (or system) already ran sender steps: logic → `runtime.report()` → HTTP POST. You receive `rawReport`, `context`, and `signatures` in the request body. | +| What does this guide cover? | Step 4: `Report.parse()` before you use `body()` or take side effects. | +| Same workflow as the sender? | Often **no**—common pattern is Workflow A (publish) and Workflow B (ingest with HTTP trigger). | + +**Receiver flow:** + +1. HTTP trigger (or your API) receives the POST payload. +2. Decode hex fields into bytes. +3. `Report.parse()` — verify signatures and read metadata. +4. Use trusted `body()` in your logic. + +For the full sender → receiver diagram, see [API Interactions — CRE reports over HTTP](/cre/guides/workflow/using-http-client#cre-reports-over-http). + +## What you'll learn + +- When to verify reports offchain vs relying on onchain forwarders +- How `Report.parse()` validates signatures and reads metadata +- How to build a receiver workflow that accepts reports over HTTP +- How to restrict verification to specific CRE environments or zones + +## Prerequisites + +- **SDK**: `@chainlink/cre-sdk` v1.8.0 or later (report verification support) +- Familiarity with [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts) (report structure and JSON payload patterns) +- For HTTP-triggered receivers: [HTTP Trigger configuration](/cre/guides/workflow/using-triggers/http-trigger/configuration-ts) + +## Onchain vs offchain verification + +| Aspect | Offchain (`Report.parse`) | Onchain (`KeystoneForwarder`) | +| -------------------- | ------------------------------------------------------------ | --------------------------------- | +| **Where it runs** | Inside your CRE workflow callback | In a smart contract transaction | +| **Signature check** | Local `ecrecover` on report hash | Contract logic onchain | +| **Signer allowlist** | Read from Capability Registry (`getDON`, `getNodesByP2PIds`) | Forwarder + registry | +| **Typical use** | HTTP APIs, webhooks, ingest workflows | Consumer contracts via `onReport` | + +Offchain verification still uses **onchain data as a trust anchor**: the first time a DON is seen, the SDK reads the production Capability Registry on Ethereum Mainnet to learn `f` and authorized signer addresses. + +Default (`productionEnvironment()`): + +- **Chain**: Ethereum Mainnet (chain selector `5009297550715157269`) +- **Registry**: `0x76c9cf548b4179F8901cda1f8623568b58215E62` + +## How verification works + +1. **Parse the report header** from `rawReport` (109-byte metadata + body). +2. **Fetch DON info** from the registry (if not cached): fault tolerance `f` and signer addresses. +3. **Verify signatures**: compute `keccak256(keccak256(rawReport) || reportContext)`, recover signers, require **f+1** valid signatures from authorized nodes. +4. **Return a `Report` object** with accessors for workflow ID, owner, execution ID, body, and more. + +If verification fails, `Report.parse()` throws (for example, unknown signer, insufficient signatures, or registry read failure). + +## Complete example: HTTP receiver workflow + +This workflow accepts a JSON payload (matching the format from [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts#pattern-4-json-formatted-report)), verifies it, then processes the trusted body. + +```typescript +import { + HTTPCapability, + handler, + Report, + type HTTPPayload, + type Runtime, + type SecretsProvider, +} from "@chainlink/cre-sdk" +import { hexToBytes } from "viem" +import { z } from "zod" + +export const configSchema = z + .object({ + authorized_key: z.string(), + }) + .transform((data) => ({ + authorizedKey: data.authorized_key, + })) + +export type Config = z.infer + +type ParsedPayload = { + report: string + context: string + signatures: string[] +} + +export async function run(runtime: Runtime, payload: HTTPPayload): Promise { + const parsed: ParsedPayload = JSON.parse(new TextDecoder().decode(payload.input)) + + const rawReport = hexToBytes(`0x${parsed.report}`) + const reportContext = hexToBytes(`0x${parsed.context}`) + const sigs = parsed.signatures.map((s) => hexToBytes(`0x${s}`)) + + const report = await Report.parse(runtime, rawReport, sigs, reportContext) + + runtime.log(`Verified report from workflow ${report.workflowId()}, execution ${report.executionId()}`) + + // Use report.body() for your application logic (ABI-encoded payload from the sender workflow) + void report.body() + + return true +} + +export const initWorkflow = (config: Config, _secretsProvider: SecretsProvider) => { + const http = new HTTPCapability() + return [ + handler(http.trigger({ authorizedKeys: [{ type: "KEY_TYPE_ECDSA_EVM", publicKey: config.authorizedKey }] }), run), + ] +} +``` + +**What's happening:** + +1. An external system POSTs hex-encoded `report`, `context`, and `signatures` to your HTTP trigger. +2. `Report.parse()` verifies signatures against the production CRE registry. +3. On success, you read metadata and `body()` safely. + +{/* prettier-ignore */} + + +## Report payload format + +Receivers need three fields (plus optional metadata your API may add): + +| Field | Description | +| ------------ | ------------------------------------------------------------------ | +| `report` | Hex-encoded `rawReport` bytes (metadata header + workflow payload) | +| `context` | Hex-encoded `reportContext` (config digest + sequence number) | +| `signatures` | Array of hex-encoded 65-byte ECDSA signatures from DON nodes | + +The `reportContext` layout used by the SDK: + +- Bytes 0–31: config digest +- Bytes 32–39: sequence number (big-endian `uint64`) + +## API reference + +See [SDK Reference: Core — Report verification](/cre/reference/sdk/core-ts#report-verification) for full signatures, types, and configuration. + +### `Report.parse()` + +```typescript +Report.parse( + runtime: Runtime, + rawReport: Uint8Array, + signatures: Uint8Array[], + reportContext: Uint8Array, + config?: ReportParseConfig, +): Promise +``` + +Parses and verifies a report. Throws if verification fails. + +### `Report` accessors + +After a successful parse: + +| Method | Description | +| ----------------- | ----------------------------------------- | +| `workflowId()` | Workflow hash (`bytes32` as hex) | +| `workflowOwner()` | Deployer address (hex) | +| `workflowName()` | Workflow name field from metadata | +| `executionId()` | Unique execution identifier | +| `donId()` | DON that produced the report | +| `timestamp()` | Report timestamp (Unix seconds) | +| `body()` | Encoded payload after the 109-byte header | +| `seqNr()` | Sequence number from report context | +| `configDigest()` | Config digest from report context | + +### `ReportParseConfig` + +```typescript +import { productionEnvironment, zoneFromEnvironment, type ReportParseConfig } from "@chainlink/cre-sdk" + +const config: ReportParseConfig = { + acceptedZones: [zoneFromEnvironment(productionEnvironment(), 1)], + acceptedEnvironments: [productionEnvironment()], + skipSignatureVerification: false, +} +``` + +| Option | Description | +| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `acceptedEnvironments` | Registry environments to check (defaults to production) | +| `acceptedZones` | Restrict to specific DON IDs within an environment | +| `skipSignatureVerification` | Parse metadata only, without registry reads or signature checks. Use only for testing or when another layer verifies signatures. There is no separate `verifySignatures()` on `Report` in TypeScript—call `Report.parse()` without this flag for production verification. | + +Most workflows should use the default config (production environment only). + +## Best practices + +1. **Verify before side effects**: Call `Report.parse()` before writing to databases, chains, or external systems. +2. **Permission on metadata**: After verification, check `workflowId()`, `workflowOwner()`, or `donId()` match your expectations. +3. **Deduplicate by execution ID**: Use `executionId()` or `keccak256(rawReport)` to reject replays (see [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts#understanding-cachesettings-for-reports)). +4. **Do not skip signature verification in production** unless you have another trust path. + +## Troubleshooting + +**`invalid signature` / `unknown signer`** + +- Signatures may be from a different DON or stale registry config. +- Confirm the sender workflow used production CRE and the report was not tampered with. + +**`wrong number of signatures`** + +- At least **f+1** valid signatures are required. Extra invalid signatures are skipped; too few valid ones fails verification. + +**`could not read from chain ...`** + +- Registry read failed (RPC/network). Retry or check simulation vs production EVM access. + +**`raw report too short`** + +- `rawReport` is missing the 109-byte metadata header. + +## Learn more + +- **[API Interactions — CRE reports over HTTP](/cre/guides/workflow/using-http-client#cre-reports-over-http)** — Sender → receiver overview +- **[Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts)** — Sender workflow: create and POST the report +- **[SDK Reference: Core — Report verification](/cre/reference/sdk/core-ts#report-verification)** — `Report.parse`, accessors, and `ReportParseConfig` +- **[HTTP Trigger Overview](/cre/guides/workflow/using-triggers/http-trigger/overview-ts)** — Trigger deployed receiver workflows +- **[Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain-ts)** — Onchain forwarder verification path +- **[Building Consumer Contracts](/cre/guides/workflow/using-evm-client/onchain-write/building-consumer-contracts-ts)** — Permissioning `onReport` with workflow metadata diff --git a/src/content/cre/llms-full-go.txt b/src/content/cre/llms-full-go.txt index c4ba2c00ff9..311ef505e98 100644 --- a/src/content/cre/llms-full-go.txt +++ b/src/content/cre/llms-full-go.txt @@ -3770,7 +3770,9 @@ writePromise := reserveManager.WriteReportFromUpdateReserves(runtime, updateData Source: https://docs.chain.link/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values Last Updated: 2025-11-04 -This guide shows how to manually generate a report containing a single value (like `uint256`, `address`, or `bool`). This is useful when you need to send a simple value onchain but don't have a struct or binding helper available. +This guide shows how to manually generate a **CRE report** containing a single value (like `uint256`, `address`, or `bool`). A CRE report is a DON-signed package from `runtime.GenerateReport()` / `runtime.report()`—your encoded data plus workflow metadata and signatures. It is **not** a [Data Streams](/data-streams) report. + +This guide covers **creating** the report (step 2 in the sender flow). **Delivering** it is a separate step—see the table below. **Use this approach when:** @@ -3790,10 +3792,12 @@ Manually generating a report for a single value involves two main steps: 1. **ABI-encode the value** into bytes using the `go-ethereum/accounts/abi` package 2. **Generate a cryptographically signed report** using `runtime.GenerateReport()` -The resulting report can then be: +| After `GenerateReport()` / `report()` | Guide | Who verifies? | +| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- | +| `evm.Client.WriteReport()` / `writeReport()` | [Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain) | `KeystoneForwarder` onchain | +| `http.Client` `SendReport()` | [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http) | Receiver: [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain) or your API | -- Submitted to the blockchain via `evm.Client.WriteReport()` (see [Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain)) -- Sent to an HTTP endpoint via `http.Client` (see [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http)) +See [API Interactions — CRE reports over HTTP](/cre/guides/workflow/using-http-client#cre-reports-over-http) for the sender → receiver mental model. +## Report verification + +Parse and verify CRE reports received over HTTP or other offchain channels. Requires **cre-sdk-go v1.8.0** or later. + + + + +Verification runs in your callback: signatures are checked offchain, while authorized signers are loaded from the onchain **Capability Registry** (default: `cre.ProductionEnvironment()` on Ethereum Mainnet). This is not the workflow deployment registry (`private` or onchain). + +### `cre.ParseReport` + +**Signature:** + +```go +func ParseReport(runtime Runtime, rawReport []byte, signatures [][]byte, reportContext []byte) (*Report, error) +``` + +Parses a report and verifies signatures against `cre.ProductionEnvironment()`. Registry reads are cached per chain and DON. + +### `cre.ParseReportWithConfig` + +**Signature:** + +```go +func ParseReportWithConfig(runtime Runtime, rawReport []byte, signatures [][]byte, reportContext []byte, config ReportParseConfig) (*Report, error) +``` + +Same as `ParseReport`, with custom accepted environments or zones. Set `SkipSignatureVerification: true` to parse metadata only; call `report.VerifySignatures()` afterward when ready. + +### `Report` accessors + +After a successful parse: + +| Method | Returns | Description | +| ----------------- | -------- | ------------------------------------------ | +| `WorkflowID()` | `string` | Workflow hash (hex) | +| `WorkflowOwner()` | `string` | Workflow owner (hex) | +| `WorkflowName()` | `string` | Workflow name from metadata | +| `ExecutionID()` | `string` | Execution identifier (hex) | +| `DONID()` | `uint32` | DON that produced the report | +| `Body()` | `[]byte` | Payload after the 109-byte metadata header | +| `SeqNr()` | `uint64` | Sequence number | +| `ConfigDigest()` | `[]byte` | Config digest | +| `ReportContext()` | `[]byte` | Full report context bytes | +| `RawReport()` | `[]byte` | Full raw report bytes | + +### `ReportParseConfig` + +| Field | Type | Description | +| --------------------------- | --------------- | ----------------------------------------------------- | +| `AcceptedEnvironments` | `[]Environment` | Registry environments to try (defaults to production) | +| `AcceptedZones` | `[]Zone` | Restrict verification to specific DON IDs | +| `SkipSignatureVerification` | `bool` | Parse header only; call `VerifySignatures` separately | + +### `cre.ProductionEnvironment` and `cre.ZoneFromEnvironment` + +```go +func ProductionEnvironment() Environment // mainnet Capability Registry +func ZoneFromEnvironment(env Environment, donId uint32) Zone +``` + +| `Environment` field | Type | Description | +| ------------------- | -------- | -------------------------------------------------------- | +| `ChainSelector` | `uint64` | Chain selector for EVM reads (default: Ethereum Mainnet) | +| `RegistryAddress` | `string` | Capability Registry contract address | + +### Verification errors + +| Error | When | +| ------------------------ | -------------------------------------------- | +| `ErrUnknownSigner` | Recovered signer not in registry allowlist | +| `ErrWrongSignatureCount` | Fewer than f+1 valid signatures | +| `ErrRawReportTooShort` | `rawReport` missing 109-byte metadata header | +| `ErrDuplicateSigner` | Same signer twice in accepted set | + ## `cre.OrderedEntries` and `cre.OrderedEntriesFunc` Go maps iterate in random order, which causes consensus failures in DON mode because different nodes process entries in different sequences. These helpers return a deterministic iterator over a map's entries, sorted by key, so all nodes process items in the same order. @@ -18629,15 +19068,17 @@ The HTTP Client lets you make requests to external APIs from your workflow. Each - Fetching data from REST APIs ([GET requests](/cre/guides/workflow/using-http-client/get-request)) - Sending data to webhooks ([POST requests](/cre/guides/workflow/using-http-client/post-request)) - Submitting reports to offchain systems ([Report submission](/cre/guides/workflow/using-http-client/submitting-reports-http)) +- Verifying reports received over HTTP ([Report verification](/cre/guides/workflow/using-http-client/verifying-reports-offchain-go)) ## Quick reference -| Method | Use When | Guide | -| ------------------------------------------------------ | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | -| [`http.SendRequest`](#httpsendrequest) | Making HTTP calls (recommended) | [GET](/cre/guides/workflow/using-http-client/get-request) / [POST](/cre/guides/workflow/using-http-client/post-request) | -| [`client.SendRequest`](#clientsendrequest) | Complex scenarios requiring fine control | [GET](/cre/guides/workflow/using-http-client/get-request#2-the-creruninnodemode-pattern-low-level) | -| [`sendRequester.SendReport`](#sendrequestersendreport) | Submitting reports via HTTP (recommended) | [Report submission](/cre/guides/workflow/using-http-client/submitting-reports-http) | -| [`client.SendReport`](#clientsendreport) | Complex report submission scenarios | [Report submission](/cre/guides/workflow/using-http-client/submitting-reports-http#advanced-low-level-pattern) | +| Method | Use When | Guide | +| ------------------------------------------------------------------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | +| [`http.SendRequest`](#httpsendrequest) | Making HTTP calls (recommended) | [GET](/cre/guides/workflow/using-http-client/get-request) / [POST](/cre/guides/workflow/using-http-client/post-request) | +| [`client.SendRequest`](#clientsendrequest) | Complex scenarios requiring fine control | [GET](/cre/guides/workflow/using-http-client/get-request#2-the-creruninnodemode-pattern-low-level) | +| [`sendRequester.SendReport`](#sendrequestersendreport) | Submitting reports via HTTP (recommended) | [Report submission](/cre/guides/workflow/using-http-client/submitting-reports-http) | +| [`client.SendReport`](#clientsendreport) | Complex report submission scenarios | [Report submission](/cre/guides/workflow/using-http-client/submitting-reports-http#advanced-low-level-pattern) | +| [`cre.ParseReport`](/cre/reference/sdk/core-go#report-verification) | Verifying received reports (receiver) | [Report verification](/cre/guides/workflow/using-http-client/verifying-reports-offchain-go) | ## Core types @@ -19009,6 +19450,11 @@ func formatReport(r *sdk.ReportResponse) (*http.Request, error) { For complete examples of including signatures in different formats (body, headers, JSON), see the [Submitting Reports via HTTP guide](/cre/guides/workflow/using-http-client/submitting-reports-http#formatting-patterns). + + + --- # SDK Reference @@ -19021,7 +19467,7 @@ This section provides a detailed technical reference for the public interfaces o The SDK Reference is broken down into several pages, each corresponding to a core part of the SDK's functionality: -- **[Core SDK](/cre/reference/sdk/core)**: Covers the fundamental building blocks of any workflow, including `cre.Handler`, `cre.Runtime`, `cre.Promise`, and map iteration helpers `cre.OrderedEntries` / `cre.OrderedEntriesFunc`. +- **[Core SDK](/cre/reference/sdk/core)**: Covers the fundamental building blocks of any workflow, including `cre.Handler`, `cre.Runtime`, `cre.Promise`, report verification (`cre.ParseReport`), and map iteration helpers `cre.OrderedEntries` / `cre.OrderedEntriesFunc`. - **[Triggers](/cre/reference/sdk/triggers)**: Details the configuration and payload structures for all available trigger types (`Cron`, `HTTP`, `EVM Log`). - **[EVM Client](/cre/reference/sdk/evm-client)**: Provides a reference for the `evm.Client`, the primary tool for all EVM interactions, including reads and writes. - **[HTTP Client](/cre/reference/sdk/http-client)**: Provides a reference for the `http.Client`, used for making offchain API requests from individual nodes. diff --git a/src/content/cre/llms-full-ts.txt b/src/content/cre/llms-full-ts.txt index f63f5974f4d..81209ecf093 100644 --- a/src/content/cre/llms-full-ts.txt +++ b/src/content/cre/llms-full-ts.txt @@ -4030,7 +4030,7 @@ if (writeResult.txStatus === TxStatus.SUCCESS) { # API Interactions Source: https://docs.chain.link/cre/guides/workflow/using-http-client -Last Updated: 2026-03-17 +Last Updated: 2026-05-20 The CRE SDK provides an HTTP client that allows your workflows to interact with external APIs. Use it to fetch offchain data, send results to other systems, or trigger external events. @@ -4046,11 +4046,40 @@ The CRE SDK provides an HTTP client that allows your workflows to interact with These guides will walk you through the common use cases for the HTTP client. +## CRE reports over HTTP + +A **CRE report** is a DON-signed package your workflow creates with `runtime.report()` (TypeScript) or `runtime.GenerateReport()` (Go). It contains your encoded payload, workflow metadata, and cryptographic signatures. It is **not** a [Data Streams](/data-streams) report—those come from a different product. + +Most secure HTTP integrations use two roles: + +```mermaid +flowchart LR + subgraph sender ["Sender workflow (this guide's focus)"] + A[Trigger] --> B[Your logic] + B --> C["runtime.report()"] + C --> D["sendReport() → HTTP POST"] + end + subgraph receiver ["Receiver (separate system)"] + D --> E[Your API or CRE HTTP trigger] + E --> F["Report.parse() before trusting data"] + end +``` + +| Step | Who | What happens | +| ---- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | +| 1 | **Sender** (CRE workflow) | Run business logic, encode payload | +| 2 | **Sender** | `runtime.report()` — DON agrees and signs | +| 3 | **Sender** | `sendReport()` — POST report bytes to your URL | +| 4 | **Receiver** | Verify signatures, then use payload — see [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain) | + +You do **not** download a report from elsewhere before submitting. The sender workflow **creates** it in step 2. Validation always happens on the **receiver** side (CRE workflow, your API, or an onchain forwarder). + ## Guides - **[Making GET Requests](/cre/guides/workflow/using-http-client/get-request)**: Learn how to fetch data from a public API using a `GET` request. - **[Making POST Requests](/cre/guides/workflow/using-http-client/post-request)**: Learn how to send data to an external endpoint using a `POST` request. - **[Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http)**: Learn how to submit cryptographically signed reports to an external HTTP endpoint. +- **[Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain)**: Verify report signatures and read workflow metadata when receiving reports over HTTP or other offchain channels. --- @@ -13988,6 +14017,17 @@ Here's the journey your workflow's data takes to reach the blockchain: In your workflow code, this process involves two steps: calling `runtime.report()` to generate the signed report, then calling `evmClient.writeReport()` to submit it to the blockchain. +### Where reports can go after generation + +The same signed report from `runtime.report()` can be delivered in different ways: + +| Destination | Guide | Verification | +| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | +| Smart contract (via Forwarder) | This section + [Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain) | Onchain in `KeystoneForwarder` | +| HTTP API | [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts) | [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts) on the receiver | + +See [CRE reports over HTTP](/cre/guides/workflow/using-http-client#cre-reports-over-http) for the sender → receiver flow. CRE reports are not [Data Streams](/data-streams) reports. + ## What you need: A consumer contract Before you can write data onchain, you need a **consumer contract**. This is the smart contract that will receive your workflow's data. @@ -14977,9 +15017,9 @@ export async function main() { # Submitting Reports via HTTP Source: https://docs.chain.link/cre/guides/workflow/using-http-client/submitting-reports-http-ts -Last Updated: 2026-01-20 +Last Updated: 2026-05-20 -This guide shows how to send a cryptographically signed report (generated by your workflow) to an external HTTP API. You'll learn how to write a transformation function that formats the report for your specific API's requirements. +This guide is for the **sender** side: a CRE workflow that **creates** a signed report and **POSTs** it to an HTTP endpoint. **What you'll learn:** @@ -14992,6 +15032,24 @@ This guide shows how to send a cryptographically signed report (generated by you This guide covers HTTP submission. For submitting reports to smart contracts, see [Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/overview-ts). +## Where this guide fits + +| Question | Answer | +| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| What is the report? | Output of `runtime.report()` after your workflow DON reaches consensus: encoded payload + metadata + signatures. Not a Data Streams report. | +| Where does it come from? | **Inside this workflow**—after your logic runs (fetch data, compute, encode). There is no separate "get report" step. | +| What does this guide cover? | Steps 2–3: generate the report, then `sendReport()` to your API. | +| Who verifies it? | The **receiver**—your HTTP service or a separate CRE workflow. See [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts). | + +**Sender flow in one workflow execution:** + +1. Trigger fires (cron, HTTP, …). +2. Your callback runs (API calls, encoding, etc.). +3. `runtime.report()` — DON produces a signed `ReportResponse`. +4. `sendReport()` — format and POST to your URL. + +For a conceptual overview and diagram, see [API Interactions — CRE reports over HTTP](/cre/guides/workflow/using-http-client#cre-reports-over-http). + ## Prerequisites - Familiarity with [making POST requests](/cre/guides/workflow/using-http-client/post-request) @@ -15527,15 +15585,13 @@ const onCronTrigger = (runtime: Runtime): MyResult => { 1. **Always use `cacheSettings`**: Include caching in every transformation function to prevent worst-case duplicate submission scenarios 2. **Implement API-side deduplication**: Your receiving API must implement deduplication using the **hash of the report** (`keccak256(rawReport)`) to detect and reject duplicate submissions -3. **Verify signatures before processing**: Your API must verify the cryptographic signatures against DON public keys before trusting report data (see note below about signature verification) +3. **Verify on the receiver**: The sender does not validate the report—your API or a [receiver CRE workflow](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts) must verify before trusting payload data 4. **Match your API's format exactly**: Study your API's documentation to understand the expected format (binary, JSON, headers, etc.) 5. **Handle errors gracefully**: Check HTTP status codes and provide meaningful error messages ## Troubleshooting @@ -15553,6 +15609,8 @@ const onCronTrigger = (runtime: Runtime): MyResult => { ## Learn more +- **[API Interactions — CRE reports over HTTP](/cre/guides/workflow/using-http-client#cre-reports-over-http)** — Sender → receiver overview +- **[Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts)** — Receiver workflow: verify before trusting payload - **[HTTP Client SDK Reference](/cre/reference/sdk/http-client-ts)** — Complete API reference including `sendReport()` and `ReportResponse` - **[POST Requests](/cre/guides/workflow/using-http-client/post-request)** — Learn about HTTP request patterns and caching - **[Writing Data Onchain](/cre/guides/workflow/using-evm-client/onchain-write/writing-data-onchain)** — Detailed guide on encoding single values, structs, and complex types using Viem @@ -15560,6 +15618,256 @@ const onCronTrigger = (runtime: Runtime): MyResult => { --- +# Verifying CRE Reports Offchain +Source: https://docs.chain.link/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts +Last Updated: 2026-05-20 + +This guide is for the **receiver** side: you already received a CRE report package (usually via HTTP) and need to **prove it is authentic** before using the payload. + +When a workflow delivers results via HTTP (or another offchain channel), nothing onchain automatically validates the report. **You must verify signatures before trusting the data.** + +The CRE SDK provides `Report.parse()` to do this inside a workflow. Verification runs **offchain** in your callback: signatures are checked with local cryptography, while authorized signer addresses are loaded via **read-only calls to the onchain Capability Registry** (default: Ethereum Mainnet). Results are cached per DON. + + + + + + + +## Where this guide fits + +| Question | Answer | +| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| What is the report? | Same CRE report the **sender** created with `runtime.report()`—not a Data Streams report. See [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts#where-this-guide-fits). | +| Where does it come from? | Another workflow (or system) already ran sender steps: logic → `runtime.report()` → HTTP POST. You receive `rawReport`, `context`, and `signatures` in the request body. | +| What does this guide cover? | Step 4: `Report.parse()` before you use `body()` or take side effects. | +| Same workflow as the sender? | Often **no**—common pattern is Workflow A (publish) and Workflow B (ingest with HTTP trigger). | + +**Receiver flow:** + +1. HTTP trigger (or your API) receives the POST payload. +2. Decode hex fields into bytes. +3. `Report.parse()` — verify signatures and read metadata. +4. Use trusted `body()` in your logic. + +For the full sender → receiver diagram, see [API Interactions — CRE reports over HTTP](/cre/guides/workflow/using-http-client#cre-reports-over-http). + +## What you'll learn + +- When to verify reports offchain vs relying on onchain forwarders +- How `Report.parse()` validates signatures and reads metadata +- How to build a receiver workflow that accepts reports over HTTP +- How to restrict verification to specific CRE environments or zones + +## Prerequisites + +- **SDK**: `@chainlink/cre-sdk` v1.8.0 or later (report verification support) +- Familiarity with [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts) (report structure and JSON payload patterns) +- For HTTP-triggered receivers: [HTTP Trigger configuration](/cre/guides/workflow/using-triggers/http-trigger/configuration-ts) + +## Onchain vs offchain verification + +| Aspect | Offchain (`Report.parse`) | Onchain (`KeystoneForwarder`) | +| -------------------- | ------------------------------------------------------------ | --------------------------------- | +| **Where it runs** | Inside your CRE workflow callback | In a smart contract transaction | +| **Signature check** | Local `ecrecover` on report hash | Contract logic onchain | +| **Signer allowlist** | Read from Capability Registry (`getDON`, `getNodesByP2PIds`) | Forwarder + registry | +| **Typical use** | HTTP APIs, webhooks, ingest workflows | Consumer contracts via `onReport` | + +Offchain verification still uses **onchain data as a trust anchor**: the first time a DON is seen, the SDK reads the production Capability Registry on Ethereum Mainnet to learn `f` and authorized signer addresses. + +Default (`productionEnvironment()`): + +- **Chain**: Ethereum Mainnet (chain selector `5009297550715157269`) +- **Registry**: `0x76c9cf548b4179F8901cda1f8623568b58215E62` + +## How verification works + +1. **Parse the report header** from `rawReport` (109-byte metadata + body). +2. **Fetch DON info** from the registry (if not cached): fault tolerance `f` and signer addresses. +3. **Verify signatures**: compute `keccak256(keccak256(rawReport) || reportContext)`, recover signers, require **f+1** valid signatures from authorized nodes. +4. **Return a `Report` object** with accessors for workflow ID, owner, execution ID, body, and more. + +If verification fails, `Report.parse()` throws (for example, unknown signer, insufficient signatures, or registry read failure). + +## Complete example: HTTP receiver workflow + +This workflow accepts a JSON payload (matching the format from [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts#pattern-4-json-formatted-report)), verifies it, then processes the trusted body. + +```typescript +import { + HTTPCapability, + handler, + Report, + type HTTPPayload, + type Runtime, + type SecretsProvider, +} from "@chainlink/cre-sdk" +import { hexToBytes } from "viem" +import { z } from "zod" + +export const configSchema = z + .object({ + authorized_key: z.string(), + }) + .transform((data) => ({ + authorizedKey: data.authorized_key, + })) + +export type Config = z.infer + +type ParsedPayload = { + report: string + context: string + signatures: string[] +} + +export async function run(runtime: Runtime, payload: HTTPPayload): Promise { + const parsed: ParsedPayload = JSON.parse(new TextDecoder().decode(payload.input)) + + const rawReport = hexToBytes(`0x${parsed.report}`) + const reportContext = hexToBytes(`0x${parsed.context}`) + const sigs = parsed.signatures.map((s) => hexToBytes(`0x${s}`)) + + const report = await Report.parse(runtime, rawReport, sigs, reportContext) + + runtime.log(`Verified report from workflow ${report.workflowId()}, execution ${report.executionId()}`) + + // Use report.body() for your application logic (ABI-encoded payload from the sender workflow) + void report.body() + + return true +} + +export const initWorkflow = (config: Config, _secretsProvider: SecretsProvider) => { + const http = new HTTPCapability() + return [ + handler(http.trigger({ authorizedKeys: [{ type: "KEY_TYPE_ECDSA_EVM", publicKey: config.authorizedKey }] }), run), + ] +} +``` + +**What's happening:** + +1. An external system POSTs hex-encoded `report`, `context`, and `signatures` to your HTTP trigger. +2. `Report.parse()` verifies signatures against the production CRE registry. +3. On success, you read metadata and `body()` safely. + + + + +## Report payload format + +Receivers need three fields (plus optional metadata your API may add): + +| Field | Description | +| ------------ | ------------------------------------------------------------------ | +| `report` | Hex-encoded `rawReport` bytes (metadata header + workflow payload) | +| `context` | Hex-encoded `reportContext` (config digest + sequence number) | +| `signatures` | Array of hex-encoded 65-byte ECDSA signatures from DON nodes | + +The `reportContext` layout used by the SDK: + +- Bytes 0–31: config digest +- Bytes 32–39: sequence number (big-endian `uint64`) + +## API reference + +See [SDK Reference: Core — Report verification](/cre/reference/sdk/core-ts#report-verification) for full signatures, types, and configuration. + +### `Report.parse()` + +```typescript +Report.parse( + runtime: Runtime, + rawReport: Uint8Array, + signatures: Uint8Array[], + reportContext: Uint8Array, + config?: ReportParseConfig, +): Promise +``` + +Parses and verifies a report. Throws if verification fails. + +### `Report` accessors + +After a successful parse: + +| Method | Description | +| ----------------- | ----------------------------------------- | +| `workflowId()` | Workflow hash (`bytes32` as hex) | +| `workflowOwner()` | Deployer address (hex) | +| `workflowName()` | Workflow name field from metadata | +| `executionId()` | Unique execution identifier | +| `donId()` | DON that produced the report | +| `timestamp()` | Report timestamp (Unix seconds) | +| `body()` | Encoded payload after the 109-byte header | +| `seqNr()` | Sequence number from report context | +| `configDigest()` | Config digest from report context | + +### `ReportParseConfig` + +```typescript +import { productionEnvironment, zoneFromEnvironment, type ReportParseConfig } from "@chainlink/cre-sdk" + +const config: ReportParseConfig = { + acceptedZones: [zoneFromEnvironment(productionEnvironment(), 1)], + acceptedEnvironments: [productionEnvironment()], + skipSignatureVerification: false, +} +``` + +| Option | Description | +| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `acceptedEnvironments` | Registry environments to check (defaults to production) | +| `acceptedZones` | Restrict to specific DON IDs within an environment | +| `skipSignatureVerification` | Parse metadata only, without registry reads or signature checks. Use only for testing or when another layer verifies signatures. There is no separate `verifySignatures()` on `Report` in TypeScript—call `Report.parse()` without this flag for production verification. | + +Most workflows should use the default config (production environment only). + +## Best practices + +1. **Verify before side effects**: Call `Report.parse()` before writing to databases, chains, or external systems. +2. **Permission on metadata**: After verification, check `workflowId()`, `workflowOwner()`, or `donId()` match your expectations. +3. **Deduplicate by execution ID**: Use `executionId()` or `keccak256(rawReport)` to reject replays (see [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts#understanding-cachesettings-for-reports)). +4. **Do not skip signature verification in production** unless you have another trust path. + +## Troubleshooting + +**`invalid signature` / `unknown signer`** + +- Signatures may be from a different DON or stale registry config. +- Confirm the sender workflow used production CRE and the report was not tampered with. + +**`wrong number of signatures`** + +- At least **f+1** valid signatures are required. Extra invalid signatures are skipped; too few valid ones fails verification. + +**`could not read from chain ...`** + +- Registry read failed (RPC/network). Retry or check simulation vs production EVM access. + +**`raw report too short`** + +- `rawReport` is missing the 109-byte metadata header. + +## Learn more + +- **[API Interactions — CRE reports over HTTP](/cre/guides/workflow/using-http-client#cre-reports-over-http)** — Sender → receiver overview +- **[Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts)** — Sender workflow: create and POST the report +- **[SDK Reference: Core — Report verification](/cre/reference/sdk/core-ts#report-verification)** — `Report.parse`, accessors, and `ReportParseConfig` +- **[HTTP Trigger Overview](/cre/guides/workflow/using-triggers/http-trigger/overview-ts)** — Trigger deployed receiver workflows +- **[Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain-ts)** — Onchain forwarder verification path +- **[Building Consumer Contracts](/cre/guides/workflow/using-evm-client/onchain-write/building-consumer-contracts-ts)** — Permissioning `onReport` with workflow metadata + +--- + # Cron Trigger Source: https://docs.chain.link/cre/guides/workflow/using-triggers/cron-trigger-ts Last Updated: 2026-01-20 @@ -17868,7 +18176,7 @@ If all nodes fail or consensus cannot be reached, the default value (`0n` in thi # SDK Reference: Core Source: https://docs.chain.link/cre/reference/sdk/core-ts -Last Updated: 2026-01-20 +Last Updated: 2026-05-20 This page provides a reference for the core data structures and functions of the CRE TypeScript SDK. These are the fundamental building blocks that every workflow uses, regardless of trigger types or capabilities. @@ -17965,7 +18273,7 @@ Both `Runtime` and `NodeRuntime` provide: - **`runInNodeMode(...)`**: Execute code on individual nodes with consensus aggregation - **`getSecret(...)`**: Access to workflow secrets -- **`report(...)`**: Generate cryptographically signed reports +- **`report(...)`**: Generate cryptographically signed reports. To verify received reports, use [`Report.parse()`](#report-verification). ### Logging @@ -18400,6 +18708,72 @@ const onTrigger = (runtime: Runtime): string => { For a complete walkthrough on creating, storing, and using secrets, see the [Secrets guide](/cre/guides/workflow/secrets). +## Report verification + +Parse and verify CRE reports received over HTTP or other offchain channels. Requires **`@chainlink/cre-sdk` v1.8.0** or later. + + + + +Verification runs in your callback: signatures are checked offchain, while authorized signers are loaded from the onchain **Capability Registry** (default: `productionEnvironment()` on Ethereum Mainnet). This is not the workflow deployment registry (`private` or onchain). + +Import from `@chainlink/cre-sdk`: `Report`, `productionEnvironment`, `zoneFromEnvironment`, and `ReportParseConfig`. + +### `Report.parse()` + +**Signature:** + +```typescript +Report.parse( + runtime: Runtime, + rawReport: Uint8Array, + signatures: Uint8Array[], + reportContext: Uint8Array, + config?: ReportParseConfig, +): Promise +``` + +Parses a report and verifies signatures. Throws on failure. Registry reads are cached per chain and DON. + +### `Report` accessors + +After a successful parse: + +| Method | Returns | Description | +| ----------------- | ------------ | ------------------------------------------ | +| `workflowId()` | `string` | Workflow hash (hex) | +| `workflowOwner()` | `string` | Workflow owner (hex) | +| `workflowName()` | `string` | Workflow name from metadata | +| `executionId()` | `string` | Execution identifier (hex) | +| `donId()` | `number` | DON that produced the report | +| `body()` | `Uint8Array` | Payload after the 109-byte metadata header | +| `seqNr()` | `bigint` | Sequence number | +| `configDigest()` | `Uint8Array` | Config digest | +| `reportContext()` | `Uint8Array` | Full report context bytes | +| `rawReport()` | `Uint8Array` | Full raw report bytes | + +### `ReportParseConfig` + +| Field | Type | Description | +| --------------------------- | --------------- | ------------------------------------------------------------------ | +| `acceptedEnvironments` | `Environment[]` | Registry environments to try (defaults to production) | +| `acceptedZones` | `Zone[]` | Restrict verification to specific DON IDs | +| `skipSignatureVerification` | `boolean` | Parse metadata only; not for production without another trust path | + +### `productionEnvironment()` and `zoneFromEnvironment()` + +```typescript +function productionEnvironment(): Environment +function zoneFromEnvironment(environment: Environment, donID: number): Zone +``` + +| `Environment` field | Type | Description | +| ------------------- | -------- | -------------------------------------------------------- | +| `chainSelector` | `bigint` | Chain selector for EVM reads (default: Ethereum Mainnet) | +| `registryAddress` | `string` | Capability Registry contract address | + --- # SDK Reference: EVM Client @@ -19374,7 +19748,7 @@ Because it operates at the node level, all HTTP requests are wrapped in a consen - **[High-level (recommended)](#high-level-sendrequest-recommended):** Automatically wraps your request in the `runtime.runInNodeMode()` pattern with consensus aggregation. - **[Low-level](#low-level-sendrequest):** Requires manual wrapping in a `runtime.runInNodeMode()` block for complex scenarios. -For complete step-by-step examples, see the [GET requests](/cre/guides/workflow/using-http-client/get-request) and [POST requests](/cre/guides/workflow/using-http-client/post-request) guides. +For complete step-by-step examples, see the [GET requests](/cre/guides/workflow/using-http-client/get-request) and [POST requests](/cre/guides/workflow/using-http-client/post-request) guides. To **submit** signed reports, see [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts). To **verify** received reports, see [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) and [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts). ## High-level `sendRequest()` (recommended) @@ -19510,6 +19884,11 @@ const submitReport = (sendRequester: HTTPSendRequester, report: Report): string } ``` + + + ## Low-level `sendRequest()` The low-level `sendRequest()` method requires manual wrapping in a `runtime.runInNodeMode()` block. It provides more flexibility for complex scenarios but requires more boilerplate code. @@ -20016,7 +20395,7 @@ This section provides a detailed technical reference for the public interfaces o The SDK Reference is broken down into several pages, each corresponding to a core part of the SDK's functionality: -- **[Core SDK](/cre/reference/sdk/core-ts)**: Covers the fundamental building blocks of any workflow, including `handler`, `Runtime`, and the `.result()` pattern for promise resolution. +- **[Core SDK](/cre/reference/sdk/core-ts)**: Covers the fundamental building blocks of any workflow, including `handler`, `Runtime`, report verification (`Report.parse`), and the `.result()` pattern for promise resolution. - **[Triggers](/cre/reference/sdk/triggers/overview-ts)**: Details the configuration and payload structures for all available trigger types (`Cron`, `HTTP`, `EVM Log`). - **[EVM Client](/cre/reference/sdk/evm-client-ts)**: Provides a reference for the `EVMClient`, the primary tool for all EVM interactions, including reads and writes. - **[HTTP Client](/cre/reference/sdk/http-client-ts)**: Provides a reference for the `HTTPClient`, used for making offchain API requests from individual nodes. diff --git a/src/content/cre/reference/sdk/core-go.mdx b/src/content/cre/reference/sdk/core-go.mdx index 8e324138bee..df48dc5c80e 100644 --- a/src/content/cre/reference/sdk/core-go.mdx +++ b/src/content/cre/reference/sdk/core-go.mdx @@ -5,9 +5,9 @@ date: Last Modified sdkLang: "go" pageId: "reference-sdk-core" metadata: - description: "Reference for core Go SDK: Workflow, Handler, Runtime, NodeRuntime, and essential functions every CRE workflow uses." + description: "Reference for core Go SDK: Workflow, Handler, Runtime, NodeRuntime, report verification (ParseReport), and essential functions every CRE workflow uses." datePublished: "2025-11-04" - lastModified: "2026-04-20" + lastModified: "2026-05-20" --- import { Aside } from "@components" @@ -321,6 +321,83 @@ func onTrigger(config *Config, runtime cre.Runtime, ...) (string, error) { guide](/cre/guides/workflow/secrets). +## Report verification + +Parse and verify CRE reports received over HTTP or other offchain channels. Requires **cre-sdk-go v1.8.0** or later. + +{/* prettier-ignore */} + + +Verification runs in your callback: signatures are checked offchain, while authorized signers are loaded from the onchain **Capability Registry** (default: `cre.ProductionEnvironment()` on Ethereum Mainnet). This is not the workflow deployment registry (`private` or onchain). + +### `cre.ParseReport` + +**Signature:** + +```go +func ParseReport(runtime Runtime, rawReport []byte, signatures [][]byte, reportContext []byte) (*Report, error) +``` + +Parses a report and verifies signatures against `cre.ProductionEnvironment()`. Registry reads are cached per chain and DON. + +### `cre.ParseReportWithConfig` + +**Signature:** + +```go +func ParseReportWithConfig(runtime Runtime, rawReport []byte, signatures [][]byte, reportContext []byte, config ReportParseConfig) (*Report, error) +``` + +Same as `ParseReport`, with custom accepted environments or zones. Set `SkipSignatureVerification: true` to parse metadata only; call `report.VerifySignatures()` afterward when ready. + +### `Report` accessors + +After a successful parse: + +| Method | Returns | Description | +| ----------------- | -------- | ------------------------------------------ | +| `WorkflowID()` | `string` | Workflow hash (hex) | +| `WorkflowOwner()` | `string` | Workflow owner (hex) | +| `WorkflowName()` | `string` | Workflow name from metadata | +| `ExecutionID()` | `string` | Execution identifier (hex) | +| `DONID()` | `uint32` | DON that produced the report | +| `Body()` | `[]byte` | Payload after the 109-byte metadata header | +| `SeqNr()` | `uint64` | Sequence number | +| `ConfigDigest()` | `[]byte` | Config digest | +| `ReportContext()` | `[]byte` | Full report context bytes | +| `RawReport()` | `[]byte` | Full raw report bytes | + +### `ReportParseConfig` + +| Field | Type | Description | +| --------------------------- | --------------- | ----------------------------------------------------- | +| `AcceptedEnvironments` | `[]Environment` | Registry environments to try (defaults to production) | +| `AcceptedZones` | `[]Zone` | Restrict verification to specific DON IDs | +| `SkipSignatureVerification` | `bool` | Parse header only; call `VerifySignatures` separately | + +### `cre.ProductionEnvironment` and `cre.ZoneFromEnvironment` + +```go +func ProductionEnvironment() Environment // mainnet Capability Registry +func ZoneFromEnvironment(env Environment, donId uint32) Zone +``` + +| `Environment` field | Type | Description | +| ------------------- | -------- | -------------------------------------------------------- | +| `ChainSelector` | `uint64` | Chain selector for EVM reads (default: Ethereum Mainnet) | +| `RegistryAddress` | `string` | Capability Registry contract address | + +### Verification errors + +| Error | When | +| ------------------------ | -------------------------------------------- | +| `ErrUnknownSigner` | Recovered signer not in registry allowlist | +| `ErrWrongSignatureCount` | Fewer than f+1 valid signatures | +| `ErrRawReportTooShort` | `rawReport` missing 109-byte metadata header | +| `ErrDuplicateSigner` | Same signer twice in accepted set | + ## `cre.OrderedEntries` and `cre.OrderedEntriesFunc` Go maps iterate in random order, which causes consensus failures in DON mode because different nodes process entries in different sequences. These helpers return a deterministic iterator over a map's entries, sorted by key, so all nodes process items in the same order. diff --git a/src/content/cre/reference/sdk/core-ts.mdx b/src/content/cre/reference/sdk/core-ts.mdx index 5b7cb3edeff..51e4ae7a929 100644 --- a/src/content/cre/reference/sdk/core-ts.mdx +++ b/src/content/cre/reference/sdk/core-ts.mdx @@ -5,9 +5,9 @@ date: Last Modified sdkLang: "ts" pageId: "reference-sdk-core" metadata: - description: "Reference for core TypeScript SDK: Workflow, Handler, Runtime, and essential functions every CRE workflow uses." + description: "Reference for core TypeScript SDK: Workflow, Handler, Runtime, report verification (Report.parse), and essential functions every CRE workflow uses." datePublished: "2025-11-04" - lastModified: "2026-01-20" + lastModified: "2026-05-20" --- import { Aside } from "@components" @@ -107,7 +107,7 @@ Both `Runtime` and `NodeRuntime` provide: - **`runInNodeMode(...)`**: Execute code on individual nodes with consensus aggregation - **`getSecret(...)`**: Access to workflow secrets -- **`report(...)`**: Generate cryptographically signed reports +- **`report(...)`**: Generate cryptographically signed reports. To verify received reports, use [`Report.parse()`](#report-verification). ### Logging @@ -542,3 +542,69 @@ const onTrigger = (runtime: Runtime): string => { + +## Report verification + +Parse and verify CRE reports received over HTTP or other offchain channels. Requires **`@chainlink/cre-sdk` v1.8.0** or later. + +{/* prettier-ignore */} + + +Verification runs in your callback: signatures are checked offchain, while authorized signers are loaded from the onchain **Capability Registry** (default: `productionEnvironment()` on Ethereum Mainnet). This is not the workflow deployment registry (`private` or onchain). + +Import from `@chainlink/cre-sdk`: `Report`, `productionEnvironment`, `zoneFromEnvironment`, and `ReportParseConfig`. + +### `Report.parse()` + +**Signature:** + +```typescript +Report.parse( + runtime: Runtime, + rawReport: Uint8Array, + signatures: Uint8Array[], + reportContext: Uint8Array, + config?: ReportParseConfig, +): Promise +``` + +Parses a report and verifies signatures. Throws on failure. Registry reads are cached per chain and DON. + +### `Report` accessors + +After a successful parse: + +| Method | Returns | Description | +| ----------------- | ------------ | ------------------------------------------ | +| `workflowId()` | `string` | Workflow hash (hex) | +| `workflowOwner()` | `string` | Workflow owner (hex) | +| `workflowName()` | `string` | Workflow name from metadata | +| `executionId()` | `string` | Execution identifier (hex) | +| `donId()` | `number` | DON that produced the report | +| `body()` | `Uint8Array` | Payload after the 109-byte metadata header | +| `seqNr()` | `bigint` | Sequence number | +| `configDigest()` | `Uint8Array` | Config digest | +| `reportContext()` | `Uint8Array` | Full report context bytes | +| `rawReport()` | `Uint8Array` | Full raw report bytes | + +### `ReportParseConfig` + +| Field | Type | Description | +| --------------------------- | --------------- | ------------------------------------------------------------------ | +| `acceptedEnvironments` | `Environment[]` | Registry environments to try (defaults to production) | +| `acceptedZones` | `Zone[]` | Restrict verification to specific DON IDs | +| `skipSignatureVerification` | `boolean` | Parse metadata only; not for production without another trust path | + +### `productionEnvironment()` and `zoneFromEnvironment()` + +```typescript +function productionEnvironment(): Environment +function zoneFromEnvironment(environment: Environment, donID: number): Zone +``` + +| `Environment` field | Type | Description | +| ------------------- | -------- | -------------------------------------------------------- | +| `chainSelector` | `bigint` | Chain selector for EVM reads (default: Ethereum Mainnet) | +| `registryAddress` | `string` | Capability Registry contract address | diff --git a/src/content/cre/reference/sdk/http-client-go.mdx b/src/content/cre/reference/sdk/http-client-go.mdx index 2db1b44cf90..4e46e4be51a 100644 --- a/src/content/cre/reference/sdk/http-client-go.mdx +++ b/src/content/cre/reference/sdk/http-client-go.mdx @@ -19,15 +19,17 @@ The HTTP Client lets you make requests to external APIs from your workflow. Each - Fetching data from REST APIs ([GET requests](/cre/guides/workflow/using-http-client/get-request)) - Sending data to webhooks ([POST requests](/cre/guides/workflow/using-http-client/post-request)) - Submitting reports to offchain systems ([Report submission](/cre/guides/workflow/using-http-client/submitting-reports-http)) +- Verifying reports received over HTTP ([Report verification](/cre/guides/workflow/using-http-client/verifying-reports-offchain-go)) ## Quick reference -| Method | Use When | Guide | -| ------------------------------------------------------ | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | -| [`http.SendRequest`](#httpsendrequest) | Making HTTP calls (recommended) | [GET](/cre/guides/workflow/using-http-client/get-request) / [POST](/cre/guides/workflow/using-http-client/post-request) | -| [`client.SendRequest`](#clientsendrequest) | Complex scenarios requiring fine control | [GET](/cre/guides/workflow/using-http-client/get-request#2-the-creruninnodemode-pattern-low-level) | -| [`sendRequester.SendReport`](#sendrequestersendreport) | Submitting reports via HTTP (recommended) | [Report submission](/cre/guides/workflow/using-http-client/submitting-reports-http) | -| [`client.SendReport`](#clientsendreport) | Complex report submission scenarios | [Report submission](/cre/guides/workflow/using-http-client/submitting-reports-http#advanced-low-level-pattern) | +| Method | Use When | Guide | +| ------------------------------------------------------------------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | +| [`http.SendRequest`](#httpsendrequest) | Making HTTP calls (recommended) | [GET](/cre/guides/workflow/using-http-client/get-request) / [POST](/cre/guides/workflow/using-http-client/post-request) | +| [`client.SendRequest`](#clientsendrequest) | Complex scenarios requiring fine control | [GET](/cre/guides/workflow/using-http-client/get-request#2-the-creruninnodemode-pattern-low-level) | +| [`sendRequester.SendReport`](#sendrequestersendreport) | Submitting reports via HTTP (recommended) | [Report submission](/cre/guides/workflow/using-http-client/submitting-reports-http) | +| [`client.SendReport`](#clientsendreport) | Complex report submission scenarios | [Report submission](/cre/guides/workflow/using-http-client/submitting-reports-http#advanced-low-level-pattern) | +| [`cre.ParseReport`](/cre/reference/sdk/core-go#report-verification) | Verifying received reports (receiver) | [Report verification](/cre/guides/workflow/using-http-client/verifying-reports-offchain-go) | ## Core types @@ -397,3 +399,8 @@ func formatReport(r *sdk.ReportResponse) (*http.Request, error) { ``` For complete examples of including signatures in different formats (body, headers, JSON), see the [Submitting Reports via HTTP guide](/cre/guides/workflow/using-http-client/submitting-reports-http#formatting-patterns). + +{/* prettier-ignore */} + diff --git a/src/content/cre/reference/sdk/http-client-ts.mdx b/src/content/cre/reference/sdk/http-client-ts.mdx index 1506ca5bfe9..5a61a390f7b 100644 --- a/src/content/cre/reference/sdk/http-client-ts.mdx +++ b/src/content/cre/reference/sdk/http-client-ts.mdx @@ -19,7 +19,7 @@ Because it operates at the node level, all HTTP requests are wrapped in a consen - **[High-level (recommended)](#high-level-sendrequest-recommended):** Automatically wraps your request in the `runtime.runInNodeMode()` pattern with consensus aggregation. - **[Low-level](#low-level-sendrequest):** Requires manual wrapping in a `runtime.runInNodeMode()` block for complex scenarios. -For complete step-by-step examples, see the [GET requests](/cre/guides/workflow/using-http-client/get-request) and [POST requests](/cre/guides/workflow/using-http-client/post-request) guides. +For complete step-by-step examples, see the [GET requests](/cre/guides/workflow/using-http-client/get-request) and [POST requests](/cre/guides/workflow/using-http-client/post-request) guides. To **submit** signed reports, see [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts). To **verify** received reports, see [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) and [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts). ## High-level `sendRequest()` (recommended) @@ -155,6 +155,11 @@ const submitReport = (sendRequester: HTTPSendRequester, report: Report): string } ``` +{/* prettier-ignore */} + + ## Low-level `sendRequest()` The low-level `sendRequest()` method requires manual wrapping in a `runtime.runInNodeMode()` block. It provides more flexibility for complex scenarios but requires more boilerplate code. diff --git a/src/content/cre/reference/sdk/overview-go.mdx b/src/content/cre/reference/sdk/overview-go.mdx index 10fcf432b8b..faa00b9dc36 100644 --- a/src/content/cre/reference/sdk/overview-go.mdx +++ b/src/content/cre/reference/sdk/overview-go.mdx @@ -18,7 +18,7 @@ This section provides a detailed technical reference for the public interfaces o The SDK Reference is broken down into several pages, each corresponding to a core part of the SDK's functionality: -- **[Core SDK](/cre/reference/sdk/core)**: Covers the fundamental building blocks of any workflow, including `cre.Handler`, `cre.Runtime`, `cre.Promise`, and map iteration helpers `cre.OrderedEntries` / `cre.OrderedEntriesFunc`. +- **[Core SDK](/cre/reference/sdk/core)**: Covers the fundamental building blocks of any workflow, including `cre.Handler`, `cre.Runtime`, `cre.Promise`, report verification (`cre.ParseReport`), and map iteration helpers `cre.OrderedEntries` / `cre.OrderedEntriesFunc`. - **[Triggers](/cre/reference/sdk/triggers)**: Details the configuration and payload structures for all available trigger types (`Cron`, `HTTP`, `EVM Log`). - **[EVM Client](/cre/reference/sdk/evm-client)**: Provides a reference for the `evm.Client`, the primary tool for all EVM interactions, including reads and writes. - **[HTTP Client](/cre/reference/sdk/http-client)**: Provides a reference for the `http.Client`, used for making offchain API requests from individual nodes. diff --git a/src/content/cre/reference/sdk/overview-ts.mdx b/src/content/cre/reference/sdk/overview-ts.mdx index ee95f7e9685..a1881db3db6 100644 --- a/src/content/cre/reference/sdk/overview-ts.mdx +++ b/src/content/cre/reference/sdk/overview-ts.mdx @@ -18,7 +18,7 @@ This section provides a detailed technical reference for the public interfaces o The SDK Reference is broken down into several pages, each corresponding to a core part of the SDK's functionality: -- **[Core SDK](/cre/reference/sdk/core-ts)**: Covers the fundamental building blocks of any workflow, including `handler`, `Runtime`, and the `.result()` pattern for promise resolution. +- **[Core SDK](/cre/reference/sdk/core-ts)**: Covers the fundamental building blocks of any workflow, including `handler`, `Runtime`, report verification (`Report.parse`), and the `.result()` pattern for promise resolution. - **[Triggers](/cre/reference/sdk/triggers/overview-ts)**: Details the configuration and payload structures for all available trigger types (`Cron`, `HTTP`, `EVM Log`). - **[EVM Client](/cre/reference/sdk/evm-client-ts)**: Provides a reference for the `EVMClient`, the primary tool for all EVM interactions, including reads and writes. - **[HTTP Client](/cre/reference/sdk/http-client-ts)**: Provides a reference for the `HTTPClient`, used for making offchain API requests from individual nodes. From 57caa9367a30d0d4f6cf0145355e95ce5111c249 Mon Sep 17 00:00:00 2001 From: devin distefano Date: Wed, 20 May 2026 19:14:32 -0500 Subject: [PATCH 2/9] cleanup --- .../generating-reports-single-values.mdx | 6 +- .../generating-reports-structs.mdx | 2 +- .../onchain-write/overview-go.mdx | 2 +- .../onchain-write/overview-ts.mdx | 2 +- .../submitting-reports-onchain.mdx | 4 +- .../workflow/using-http-client/index.mdx | 40 +++---- .../submitting-reports-http-go.mdx | 34 +++--- .../submitting-reports-http-ts.mdx | 32 ++--- .../verifying-reports-offchain-go.mdx | 30 ++--- .../verifying-reports-offchain-ts.mdx | 42 +++---- src/content/cre/llms-full-go.txt | 110 ++++++++---------- src/content/cre/llms-full-ts.txt | 110 ++++++++---------- .../cre/reference/sdk/http-client-ts.mdx | 2 +- 13 files changed, 193 insertions(+), 223 deletions(-) diff --git a/src/content/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values.mdx b/src/content/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values.mdx index a505db987cd..ac36c54224b 100644 --- a/src/content/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values.mdx +++ b/src/content/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values.mdx @@ -11,9 +11,9 @@ metadata: import { Aside } from "@components" -This guide shows how to manually generate a **CRE report** containing a single value (like `uint256`, `address`, or `bool`). A CRE report is a DON-signed package from `runtime.GenerateReport()` / `runtime.report()`—your encoded data plus workflow metadata and signatures. It is **not** a [Data Streams](/data-streams) report. +This guide shows how to manually generate a **CRE report** containing a single value (like `uint256`, `address`, or `bool`). A CRE report is a DON-signed package from `runtime.GenerateReport()` / `runtime.report()`: your encoded data plus workflow metadata and signatures. -This guide covers **creating** the report (step 2 in the sender flow). **Delivering** it is a separate step—see the table below. +This guide covers **creating** the signed report (`GenerateReport()` / `runtime.report()`). **Delivering** it is a separate step: see the table below. **Use this approach when:** @@ -38,7 +38,7 @@ Manually generating a report for a single value involves two main steps: | `evm.Client.WriteReport()` / `writeReport()` | [Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain) | `KeystoneForwarder` onchain | | `http.Client` `SendReport()` | [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http) | Receiver: [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain) or your API | -See [API Interactions — CRE reports over HTTP](/cre/guides/workflow/using-http-client#cre-reports-over-http) for the sender → receiver mental model. +See [API Interactions: CRE reports over HTTP](/cre/guides/workflow/using-http-client#cre-reports-over-http) for the sender → receiver mental model. +## What is a CRE report? + +A **[CRE report](/cre/key-terms#report-cre-report)** is a DON-signed package another workflow (or system) created with [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values). You receive its bytes over HTTP (or another channel) as `rawReport`, `reportContext`, and `signatures`. Before you use the encoded payload, you must confirm the signatures match authorized DON signers on the Capability Registry. + +This guide is the **receiver** path: decode the POST body, call [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport), then read trusted metadata and `Body()`. See [Key Terms: Report](/cre/key-terms#report-cre-report) for how reports are created and delivered. + ## Where this guide fits -| Question | Answer | -| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| What is the report? | Same CRE report the **sender** created with `runtime.GenerateReport()`. See [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-go#where-this-guide-fits). | -| Where does it come from? | Another workflow (or system) already ran sender steps: logic → `GenerateReport()` → HTTP POST. You receive `rawReport`, `context`, and `signatures` in the request body. | -| What does this guide cover? | Step 3 below: `cre.ParseReport()` before you use `Body()` or take side effects. | -| Same workflow as the sender? | Often **no:** common pattern is Workflow A (publish) and Workflow B (ingest with HTTP trigger). | +| Question | Answer | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| What is the report? | Same [CRE report](/cre/key-terms#report-cre-report) the **sender** created with [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values). See [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-go#what-is-a-cre-report). | +| Where does it come from? | Another workflow (or system) already ran sender steps: logic → [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) → HTTP POST. You receive `rawReport`, `context`, and `signatures` in the request body. | +| What does this guide cover? | Step 3 below: [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) before you use `Body()` or take side effects. | +| Same workflow as the sender? | Often **no:** common pattern is Workflow A (publish) and Workflow B (ingest with HTTP trigger). | **Receiver flow:** 1. HTTP trigger (or your API) receives the POST payload. 2. Decode hex fields into bytes. -3. `cre.ParseReport()`: verify signatures and read metadata. +3. [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport): verify signatures and read metadata. 4. Use trusted `Body()` in your logic. Pair this guide with [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-go) on the sender side. This guide covers local simulation first, then the deploy example with `AuthorizedKeys`. @@ -49,7 +55,7 @@ Pair this guide with [Submitting Reports via HTTP](/cre/guides/workflow/using-ht ## What you'll learn - When to verify reports offchain vs relying on onchain forwarders -- How `cre.ParseReport()` validates signatures and reads metadata +- How [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) validates signatures and reads metadata - How to build a receiver workflow that accepts reports over HTTP - How to restrict verification to specific CRE environments or zones @@ -61,12 +67,12 @@ Pair this guide with [Submitting Reports via HTTP](/cre/guides/workflow/using-ht ## Onchain vs offchain verification -| Aspect | Offchain (`cre.ParseReport`) | Onchain (`KeystoneForwarder`) | -| -------------------- | ------------------------------------------------------------ | --------------------------------- | -| **Where it runs** | Inside your CRE workflow callback | In a smart contract transaction | -| **Signature check** | Local `ecrecover` on report hash | Contract logic onchain | -| **Signer allowlist** | Read from Capability Registry (`getDON`, `getNodesByP2PIds`) | Forwarder + registry | -| **Typical use** | HTTP APIs, webhooks, ingest workflows | Consumer contracts via `onReport` | +| Aspect | Offchain ([`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport)) | Onchain (`KeystoneForwarder`) | +| -------------------- | --------------------------------------------------------------------------- | --------------------------------- | +| **Where it runs** | Inside your CRE workflow callback | In a smart contract transaction | +| **Signature check** | Local `ecrecover` on report hash | Contract logic onchain | +| **Signer allowlist** | Read from Capability Registry (`getDON`, `getNodesByP2PIds`) | Forwarder + registry | +| **Typical use** | HTTP APIs, webhooks, ingest workflows | Consumer contracts via `onReport` | Offchain verification still uses **onchain data as a trust anchor**: the first time a DON is seen, the SDK reads the production Capability Registry on Ethereum Mainnet to learn `f` and authorized signer addresses. @@ -82,7 +88,7 @@ Default (`cre.ProductionEnvironment()`): 3. **Verify signatures**: compute `keccak256(keccak256(rawReport) || reportContext)`, recover signers, require **f+1** valid signatures from authorized nodes. 4. **Return a `*cre.Report`** with accessors for workflow ID, owner, execution ID, body, and more. -If verification fails, `cre.ParseReport()` returns an error (for example, `ErrUnknownSigner`, `ErrWrongSignatureCount`, or registry read failure). +If verification fails, [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) returns an error (for example, `ErrUnknownSigner`, `ErrWrongSignatureCount`, or registry read failure). ## Testing locally with simulation @@ -94,7 +100,7 @@ After you run the [submit guide complete example](/cre/guides/workflow/using-htt ### Minimal receiver for simulation -Use an empty HTTP trigger for sim. Set `SkipSignatureVerification: true` in staging config (or pass it to `ParseReportWithConfig`). The CLI delivers `--http-payload` file contents as `payload.Input` bytes. +Use an empty HTTP trigger for sim. Set `SkipSignatureVerification: true` in staging config (or pass it to [`ParseReportWithConfig`](/cre/reference/sdk/core-go#creparsereportwithconfig)). The CLI delivers `--http-payload` file contents as `payload.Input` bytes. `config.staging.json`: @@ -183,10 +189,10 @@ func main() { ### Sim wiring vs full verify -| Mode | Config | What it validates | -| ---------------------- | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------- | -| **Wiring / decode** | `SkipSignatureVerification: true` in `ParseReportWithConfig` | JSON + hex decode, metadata accessors, `Body()` | -| **Full crypto verify** | Default `cre.ParseReport()` (production registry) | Reports from a **deployed/production DON**. Sim-signed reports **fail** default verification. | +| Mode | Config | What it validates | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | +| **Wiring / decode** | `SkipSignatureVerification: true` in [`ParseReportWithConfig`](/cre/reference/sdk/core-go#creparsereportwithconfig) | JSON + hex decode, metadata accessors, `Body()` | +| **Full crypto verify** | Default [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) (production registry) | Reports from a **deployed/production DON**. Sim-signed reports **fail** default verification. | ```bash cre workflow simulate verify-report-receiver \ @@ -198,8 +204,8 @@ cre workflow simulate verify-report-receiver \ **Pass criteria** -- **Sim wiring:** `SkipSignatureVerification: true` in `ParseReportWithConfig`: logs show metadata and a successful handler return. -- **Full crypto verify:** default `ParseReport` with a **production-signed** report (not typical for sender-sim → receiver-sim alone). +- **Sim wiring:** `SkipSignatureVerification: true` in [`ParseReportWithConfig`](/cre/reference/sdk/core-go#creparsereportwithconfig): logs show metadata and a successful handler return. +- **Full crypto verify:** default [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) with a **production-signed** report (not typical for sender-sim → receiver-sim alone). `project.yaml` needs **`ethereum-mainnet` RPC** for default verify (registry reads). @@ -303,7 +309,7 @@ func main() { **What's happening:** 1. An external system POSTs hex-encoded `report`, `context`, and `signatures` to your HTTP trigger. -2. `cre.ParseReport()` verifies signatures against the production CRE registry. +2. [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) verifies signatures against the production CRE registry. 3. On success, you read metadata and `Body()` safely. {/* prettier-ignore */} @@ -313,7 +319,7 @@ func main() { ## Report payload format -Receivers need three JSON fields. The JSON key is `context` even though the SDK field is `ReportContext`: +When a sender POSTs a [CRE report](/cre/key-terms#report-cre-report) as JSON for offchain verification, receivers need three fields. The JSON key is `context` even though the SDK field is `ReportContext`: | JSON field | SDK field | Description | | ------------ | --------------- | --------------------------------------------------------------- | @@ -336,7 +342,7 @@ See [SDK Reference: Core: Report verification](/cre/reference/sdk/core-go#report func ParseReport(runtime Runtime, rawReport []byte, signatures [][]byte, reportContext []byte) (*Report, error) ``` -Parses and verifies a report against the production CRE environment. Use `ParseReportWithConfig` for custom environments or zones. +Parses and verifies a report against the production CRE environment. Use [`ParseReportWithConfig`](/cre/reference/sdk/core-go#creparsereportwithconfig) for custom environments or zones. ### `*cre.Report` accessors @@ -377,7 +383,7 @@ report, err := cre.ParseReportWithConfig(runtime, rawReport, sigs, reportContext This is a **different pattern** from the simulation testing use of `SkipSignatureVerification`. In testing, you skip verification permanently. Here, you parse the header first to inspect metadata (such as `WorkflowID()` or `DONID()` for filtering), then call `VerifySignatures` in a separate step — useful when you want to gate registry reads on workflow identity checks. -If you set `SkipSignatureVerification: true` in `ParseReportWithConfig`, parse the header first, then verify: +If you set `SkipSignatureVerification: true` in [`ParseReportWithConfig`](/cre/reference/sdk/core-go#creparsereportwithconfig), parse the header first, then verify: ```go report, err := cre.ParseReportWithConfig(runtime, rawReport, sigs, reportContext, cre.ReportParseConfig{ @@ -396,7 +402,7 @@ if err := report.VerifySignatures(runtime); err != nil { ## Best practices -1. **Verify before side effects**: Call `cre.ParseReport()` before writing to databases, chains, or external systems. +1. **Verify before side effects**: Call [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) before writing to databases, chains, or external systems. 2. **Permission on metadata**: After verification, check `WorkflowID()`, `WorkflowOwner()`, or `DONID()` match your expectations. 3. **Deduplicate by execution ID**: Use `ExecutionID()` or `keccak256(rawReport)` to reject replays (see [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-go#understanding-cachesettings-for-reports)). 4. **Do not skip signature verification in production** unless you have another trust path. @@ -405,7 +411,7 @@ if err := report.VerifySignatures(runtime); err != nil { **`ErrUnknownSigner` / `invalid signature` in sim with fresh webhook JSON** -- **Expected** when using default `ParseReport` on a **sim-signed** report: simulator DON keys do not match mainnet registry signers. +- **Expected** when using default [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) on a **sim-signed** report: simulator DON keys do not match mainnet registry signers. - For local wiring tests, use `SkipSignatureVerification: true`. For real crypto verify, use a **deployed sender** or production-signed reports. **`ErrUnknownSigner` (deployed)** diff --git a/src/content/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts.mdx b/src/content/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts.mdx index a42faaa3f30..ed6a799d911 100644 --- a/src/content/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts.mdx +++ b/src/content/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts.mdx @@ -16,11 +16,11 @@ This guide is for the **receiver** side: you already received a CRE report packa When a workflow delivers results via HTTP (or another offchain channel), nothing onchain automatically validates the report. **You must verify signatures before trusting the data.** -The CRE SDK provides `Report.parse()` to do this inside a workflow. Verification runs **offchain** in your callback: signatures are checked with local cryptography, while authorized signer addresses are loaded via **read-only calls to the onchain Capability Registry** (default: Ethereum Mainnet). Results are cached per DON. +The CRE SDK provides [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) to do this inside a workflow. Verification runs **offchain** in your callback: signatures are checked with local cryptography, while authorized signer addresses are loaded via **read-only calls to the onchain Capability Registry** (default: Ethereum Mainnet). Results are cached per DON. {/* prettier-ignore */} {/* prettier-ignore */} @@ -28,20 +28,26 @@ The CRE SDK provides `Report.parse()` to do this inside a workflow. Verification When you submit reports onchain through the `KeystoneForwarder`, the forwarder contract verifies signatures before calling your consumer's `onReport`. This guide covers **offchain** verification for HTTP and custom ingest paths. See [Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain). +## What is a CRE report? + +A **[CRE report](/cre/key-terms#report-cre-report)** is a DON-signed package another workflow (or system) created with [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime). You receive its bytes over HTTP (or another channel) as `rawReport`, `reportContext`, and `signatures`. Before you use the encoded payload, you must confirm the signatures match authorized DON signers on the Capability Registry. + +This guide is the **receiver** path: decode the POST body, call [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification), then read trusted metadata and `body()`. See [Key Terms: Report](/cre/key-terms#report-cre-report) for how reports are created and delivered. + ## Where this guide fits -| Question | Answer | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| What is the report? | Same CRE report the **sender** created with `runtime.report()`. See [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts#where-this-guide-fits). | -| Where does it come from? | Another workflow (or system) already ran sender steps: logic → `runtime.report()` → HTTP POST. You receive `rawReport`, `context`, and `signatures` in the request body. | -| What does this guide cover? | Step 3 below: `Report.parse()` before you use `body()` or take side effects. | -| Same workflow as the sender? | Often **no:** common pattern is Workflow A (publish) and Workflow B (ingest with HTTP trigger). | +| Question | Answer | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| What is the report? | Same [CRE report](/cre/key-terms#report-cre-report) the **sender** created with [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime). See [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts#what-is-a-cre-report). | +| Where does it come from? | Another workflow (or system) already ran sender steps: logic → [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) → HTTP POST. You receive `rawReport`, `context`, and `signatures` in the request body. | +| What does this guide cover? | Step 3 below: [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) before you use `body()` or take side effects. | +| Same workflow as the sender? | Often **no:** common pattern is Workflow A (publish) and Workflow B (ingest with HTTP trigger). | **Receiver flow:** 1. HTTP trigger (or your API) receives the POST payload. 2. Decode hex fields into bytes. -3. `Report.parse()`: verify signatures and read metadata. +3. [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification): verify signatures and read metadata. 4. Use trusted `body()` in your logic. Pair this guide with [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts) on the sender side. This guide covers local simulation first, then the deploy example with `authorizedKeys`. @@ -49,7 +55,7 @@ Pair this guide with [Submitting Reports via HTTP](/cre/guides/workflow/using-ht ## What you'll learn - When to verify reports offchain vs relying on onchain forwarders -- How `Report.parse()` validates signatures and reads metadata +- How [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) validates signatures and reads metadata - How to build a receiver workflow that accepts reports over HTTP - How to restrict verification to specific CRE environments or zones @@ -61,12 +67,12 @@ Pair this guide with [Submitting Reports via HTTP](/cre/guides/workflow/using-ht ## Onchain vs offchain verification -| Aspect | Offchain (`Report.parse`) | Onchain (`KeystoneForwarder`) | -| -------------------- | ------------------------------------------------------------ | --------------------------------- | -| **Where it runs** | Inside your CRE workflow callback | In a smart contract transaction | -| **Signature check** | Local `ecrecover` on report hash | Contract logic onchain | -| **Signer allowlist** | Read from Capability Registry (`getDON`, `getNodesByP2PIds`) | Forwarder + registry | -| **Typical use** | HTTP APIs, webhooks, ingest workflows | Consumer contracts via `onReport` | +| Aspect | Offchain ([`Report.parse()`](/cre/reference/sdk/core-ts#report-verification)) | Onchain (`KeystoneForwarder`) | +| -------------------- | ----------------------------------------------------------------------------- | --------------------------------- | +| **Where it runs** | Inside your CRE workflow callback | In a smart contract transaction | +| **Signature check** | Local `ecrecover` on report hash | Contract logic onchain | +| **Signer allowlist** | Read from Capability Registry (`getDON`, `getNodesByP2PIds`) | Forwarder + registry | +| **Typical use** | HTTP APIs, webhooks, ingest workflows | Consumer contracts via `onReport` | Offchain verification still uses **onchain data as a trust anchor**: the first time a DON is seen, the SDK reads the production Capability Registry on Ethereum Mainnet to learn `f` and authorized signer addresses. @@ -82,7 +88,7 @@ Default (`productionEnvironment()`): 3. **Verify signatures**: compute `keccak256(keccak256(rawReport) || reportContext)`, recover signers, require **f+1** valid signatures from authorized nodes. 4. **Return a `Report` object** with accessors for workflow ID, owner, execution ID, body, and more. -If verification fails, `Report.parse()` throws (for example, unknown signer, insufficient signatures, or registry read failure). +If verification fails, [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) throws (for example, unknown signer, insufficient signatures, or registry read failure). ## Testing locally with simulation @@ -94,14 +100,14 @@ After you run the [submit guide complete example](/cre/guides/workflow/using-htt ### Sim wiring vs full verify -| Mode | Config | What it validates | -| ---------------------- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Wiring / decode** | `skipSignatureVerification: true` in staging config | JSON + hex decode, `workflowId()`, `executionId()`, `donId()`, `body()` | -| **Full crypto verify** | Default `Report.parse()` (production registry) | Reports from a **deployed/production DON**. Sim-signed reports **fail** default verification: simulation uses local DON keys; `Report.parse()` checks the mainnet Capability Registry. | +| Mode | Config | What it validates | +| ---------------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Wiring / decode** | `skipSignatureVerification: true` in staging config | JSON + hex decode, `workflowId()`, `executionId()`, `donId()`, `body()` | +| **Full crypto verify** | Default [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) (production registry) | Reports from a **deployed/production DON**. Sim-signed reports **fail** default verification: simulation uses local DON keys; [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) checks the mainnet Capability Registry. | ### Minimal receiver for simulation -Use an **empty HTTP trigger config** for sim (add `authorizedKeys` before deploy). Call `Report.parse()` from your handler with the `runtime` parameter. The CLI delivers `--http-payload` file contents as `payload.input` bytes. +Use an **empty HTTP trigger config** for sim (add `authorizedKeys` before deploy). Call [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) from your handler with the `runtime` parameter. The CLI delivers `--http-payload` file contents as `payload.input` bytes. `config.staging.json`: @@ -262,7 +268,7 @@ export const initWorkflow = (config: Config, _secretsProvider: SecretsProvider) **What's happening:** 1. An external system POSTs hex-encoded `report`, `context`, and `signatures` to your HTTP trigger. -2. `Report.parse()` verifies signatures against the production CRE registry. +2. [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) verifies signatures against the production CRE registry. 3. On success, you read metadata and `body()` safely. {/* prettier-ignore */} @@ -272,7 +278,7 @@ export const initWorkflow = (config: Config, _secretsProvider: SecretsProvider) ## Report payload format -Receivers need three JSON fields (plus optional metadata your API may add). The JSON key is `context` even though the SDK field is `reportContext`: +When a sender POSTs a [CRE report](/cre/key-terms#report-cre-report) as JSON for offchain verification, receivers need three fields (plus optional metadata your API may add). The JSON key is `context` even though the SDK field is `reportContext`: | JSON field | SDK field | Description | | ------------ | --------------- | --------------------------------------------------------------- | @@ -331,17 +337,17 @@ const config: ReportParseConfig = { } ``` -| Option | Description | -| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `acceptedEnvironments` | Registry environments to check (defaults to production) | -| `acceptedZones` | Restrict to specific DON IDs within an environment | -| `skipSignatureVerification` | Parse metadata only, without registry reads or signature checks. Use only for testing or when another layer verifies signatures. There is no separate `verifySignatures()` on `Report` in TypeScript; call `Report.parse()` without this flag for production verification. | +| Option | Description | +| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `acceptedEnvironments` | Registry environments to check (defaults to production) | +| `acceptedZones` | Restrict to specific DON IDs within an environment | +| `skipSignatureVerification` | Parse metadata only, without registry reads or signature checks. Use only for testing or when another layer verifies signatures. There is no separate `verifySignatures()` on `Report` in TypeScript; call [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) without this flag for production verification. | Most workflows should use the default config (production environment only). ## Best practices -1. **Verify before side effects**: Call `Report.parse()` before writing to databases, chains, or external systems. +1. **Verify before side effects**: Call [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) before writing to databases, chains, or external systems. 2. **Permission on metadata**: After verification, check `workflowId()`, `workflowOwner()`, or `donId()` match your expectations. 3. **Deduplicate by execution ID**: Use `executionId()` or `keccak256(rawReport)` to reject replays (see [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts#understanding-cachesettings-for-reports)). 4. **Do not skip signature verification in production** unless you have another trust path. @@ -350,12 +356,12 @@ Most workflows should use the default config (production environment only). **Empty error after verify sim** -- `Report.parse()` may throw an **`AggregateError`** of multiple `invalid signature` errors. **`AggregateError.message` is often empty**, so the CLI prints `Execution resulted in an error being returned:` with nothing after the colon. +- [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) may throw an **`AggregateError`** of multiple `invalid signature` errors. **`AggregateError.message` is often empty**, so the CLI prints `Execution resulted in an error being returned:` with nothing after the colon. - Format errors in your handler before rethrowing (see the simulation example above). **`invalid signature` / `unknown signer` in sim with fresh webhook JSON** -- **Expected** when using default `Report.parse()` on a **sim-signed** report: simulator DON keys do not match mainnet registry signers. +- **Expected** when using default [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) on a **sim-signed** report: simulator DON keys do not match mainnet registry signers. - For local wiring tests, set `skipSignatureVerification: true`. For real crypto verify, use a **deployed sender** or production-signed reports. **`invalid signature` / `unknown signer` (deployed)** diff --git a/src/content/cre/index.mdx b/src/content/cre/index.mdx index e0d87181230..7f7432d5c44 100644 --- a/src/content/cre/index.mdx +++ b/src/content/cre/index.mdx @@ -110,17 +110,18 @@ Learn more about [Consensus Computing in CRE](/cre/concepts/consensus-computing) ## Glossary: Building blocks -| Concept | One-liner | -| ------------------ | ----------------------------------------------------------------- | -| **Workflow** | Compiled WebAssembly (WASM) binary. | -| **Handler** | `handler(trigger, callback)` pair; the atom of execution. | -| **Trigger** | Event that starts an execution (cron, HTTP, EVM log, …). | -| **Callback** | Function that runs when its trigger fires; contains your logic. | -| **Runtime** | Object passed to a callback; used to invoke capabilities. | -| **Capability** | Decentralized microservice (chain read/write, HTTP Fetch, ...). | -| **Workflow DON** | Watches triggers and coordinates the workflow. | -| **Capability DON** | Executes a specific capability. | -| **Consensus** | BFT protocol that merges node results into one verifiable report. | +| Concept | One-liner | +| ------------------ | ---------------------------------------------------------------- | +| **Workflow** | Compiled WebAssembly (WASM) binary. | +| **Handler** | `handler(trigger, callback)` pair; the atom of execution. | +| **Trigger** | Event that starts an execution (cron, HTTP, EVM log, …). | +| **Callback** | Function that runs when its trigger fires; contains your logic. | +| **Runtime** | Object passed to a callback; used to invoke capabilities. | +| **Capability** | Decentralized microservice (chain read/write, HTTP Fetch, ...). | +| **Workflow DON** | Watches triggers and coordinates the workflow. | +| **Capability DON** | Executes a specific capability. | +| **Consensus** | BFT protocol that merges node results into one trusted outcome. | +| **Report** | DON-signed package from `runtime.report()` / `GenerateReport()`. | Full definitions live on **[Key Terms and Concepts](/cre/key-terms)**. diff --git a/src/content/cre/key-terms.mdx b/src/content/cre/key-terms.mdx index 722c51ddc98..98a95fb4b12 100644 --- a/src/content/cre/key-terms.mdx +++ b/src/content/cre/key-terms.mdx @@ -5,7 +5,7 @@ date: Last Modified metadata: description: "Learn essential CRE concepts: workflows, handlers, triggers, callbacks, Runtime, capabilities, DONs, and consensus." datePublished: "2025-11-04" - lastModified: "2025-11-04" + lastModified: "2026-05-20" --- import { Aside } from "@components" @@ -75,6 +75,12 @@ Short-lived objects passed to your callback function during a specific execution Learn more about [Consensus and Aggregation](/cre/reference/sdk/consensus). +### Report (CRE report) + +A cryptographically signed package your workflow [DON](/cre/key-terms#decentralized-oracle-network-don) produces after your callback encodes its result: payload bytes, report context, and DON signatures. Create it with [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) (TypeScript) or [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) (Go). + +Deliver onchain with [`writeReport()`](/cre/reference/sdk/evm-client-ts#writereport) / [`WriteReport()`](/cre/reference/sdk/evm-client-go#writereport), or offchain with [`sendReport()`](/cre/reference/sdk/http-client-ts#using-sendreport) / [`SendReport()`](/cre/reference/sdk/http-client-go#sendrequestersendreport). Receivers verify with [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) or [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport). See [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http) and [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain). + ### SDK Clients: `EVMClient` & `HTTPClient` The primary SDK clients you use inside a callback to interact with capabilities. For example, you use an EVM client to read from a smart contract and an HTTP client to make offchain API requests. diff --git a/src/content/cre/llms-full-go.txt b/src/content/cre/llms-full-go.txt index 175df69db76..fde695841ea 100644 --- a/src/content/cre/llms-full-go.txt +++ b/src/content/cre/llms-full-go.txt @@ -99,17 +99,18 @@ Learn more about [Consensus Computing in CRE](/cre/concepts/consensus-computing) ## Glossary: Building blocks -| Concept | One-liner | -| ------------------ | ----------------------------------------------------------------- | -| **Workflow** | Compiled WebAssembly (WASM) binary. | -| **Handler** | `handler(trigger, callback)` pair; the atom of execution. | -| **Trigger** | Event that starts an execution (cron, HTTP, EVM log, …). | -| **Callback** | Function that runs when its trigger fires; contains your logic. | -| **Runtime** | Object passed to a callback; used to invoke capabilities. | -| **Capability** | Decentralized microservice (chain read/write, HTTP Fetch, ...). | -| **Workflow DON** | Watches triggers and coordinates the workflow. | -| **Capability DON** | Executes a specific capability. | -| **Consensus** | BFT protocol that merges node results into one verifiable report. | +| Concept | One-liner | +| ------------------ | ---------------------------------------------------------------- | +| **Workflow** | Compiled WebAssembly (WASM) binary. | +| **Handler** | `handler(trigger, callback)` pair; the atom of execution. | +| **Trigger** | Event that starts an execution (cron, HTTP, EVM log, …). | +| **Callback** | Function that runs when its trigger fires; contains your logic. | +| **Runtime** | Object passed to a callback; used to invoke capabilities. | +| **Capability** | Decentralized microservice (chain read/write, HTTP Fetch, ...). | +| **Workflow DON** | Watches triggers and coordinates the workflow. | +| **Capability DON** | Executes a specific capability. | +| **Consensus** | BFT protocol that merges node results into one trusted outcome. | +| **Report** | DON-signed package from `runtime.report()` / `GenerateReport()`. | Full definitions live on **[Key Terms and Concepts](/cre/key-terms)**. @@ -149,7 +150,7 @@ Jump to what you need: # Key Terms and Concepts Source: https://docs.chain.link/cre/key-terms -Last Updated: 2025-11-04 +Last Updated: 2026-05-20 This page defines the fundamental terms and concepts for the Chainlink Runtime Environment (CRE). @@ -216,6 +217,12 @@ Short-lived objects passed to your callback function during a specific execution Learn more about [Consensus and Aggregation](/cre/reference/sdk/consensus). +### Report (CRE report) + +A cryptographically signed package your workflow [DON](/cre/key-terms#decentralized-oracle-network-don) produces after your callback encodes its result: payload bytes, report context, and DON signatures. Create it with [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) (TypeScript) or [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) (Go). + +Deliver onchain with [`writeReport()`](/cre/reference/sdk/evm-client-ts#writereport) / [`WriteReport()`](/cre/reference/sdk/evm-client-go#writereport), or offchain with [`sendReport()`](/cre/reference/sdk/http-client-ts#using-sendreport) / [`SendReport()`](/cre/reference/sdk/http-client-go#sendrequestersendreport). Receivers verify with [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) or [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport). See [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http) and [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain). + ### SDK Clients: `EVMClient` & `HTTPClient` The primary SDK clients you use inside a callback to interact with capabilities. For example, you use an EVM client to read from a smart contract and an HTTP client to make offchain API requests. @@ -3770,9 +3777,9 @@ writePromise := reserveManager.WriteReportFromUpdateReserves(runtime, updateData Source: https://docs.chain.link/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values Last Updated: 2025-11-04 -This guide shows how to manually generate a **CRE report** containing a single value (like `uint256`, `address`, or `bool`). A CRE report is a DON-signed package from `runtime.GenerateReport()` / `runtime.report()`: your encoded data plus workflow metadata and signatures. +This guide shows how to manually generate a **[CRE report](/cre/key-terms#report-cre-report)** containing a single value (like `uint256`, `address`, or `bool`). See [Key Terms: Report](/cre/key-terms#report-cre-report) for the full definition; in short, it is a DON-signed package from [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) / [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) with your encoded data, workflow metadata, and signatures. -This guide covers **creating** the signed report (`GenerateReport()` / `runtime.report()`). **Delivering** it is a separate step: see the table below. +This guide covers **creating** the signed report ([`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) / [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime)). **Delivering** it is a separate step: see the table below. **Use this approach when:** @@ -3790,12 +3797,12 @@ This guide covers **creating** the signed report (`GenerateReport()` / `runtime. Manually generating a report for a single value involves two main steps: 1. **ABI-encode the value** into bytes using the `go-ethereum/accounts/abi` package -2. **Generate a cryptographically signed report** using `runtime.GenerateReport()` +2. **Generate a cryptographically signed report** using [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) -| After `GenerateReport()` / `report()` | Guide | Who verifies? | -| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- | -| `evm.Client.WriteReport()` / `writeReport()` | [Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain) | `KeystoneForwarder` onchain | -| `http.Client` `SendReport()` | [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http) | Receiver: [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain) or your API | +| After [`GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) / [`report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) | Guide | Who verifies? | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- | +| [`WriteReport()`](/cre/reference/sdk/evm-client-go#writereport) / [`writeReport()`](/cre/reference/sdk/evm-client-ts#writereport) | [Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain) | `KeystoneForwarder` onchain | +| [`SendReport()`](/cre/reference/sdk/http-client-go#sendrequestersendreport) / [`sendReport()`](/cre/reference/sdk/http-client-ts#using-sendreport) | [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http) | Receiver: [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain) or your API | See [API Interactions: CRE reports over HTTP](/cre/guides/workflow/using-http-client#cre-reports-over-http) for the sender → receiver mental model. @@ -4036,7 +4043,7 @@ func main() { Source: https://docs.chain.link/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-structs Last Updated: 2025-11-04 -This guide shows how to generate a **CRE report** containing a struct with multiple fields. A CRE report is a DON-signed package from `runtime.GenerateReport()`. After generation, deliver it [onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain) or via [HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http); HTTP receivers should [verify offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain). See [API Interactions: CRE reports over HTTP](/cre/guides/workflow/using-http-client#cre-reports-over-http). +This guide shows how to generate a **[CRE report](/cre/key-terms#report-cre-report)** containing a struct with multiple fields. See [Key Terms: Report](/cre/key-terms#report-cre-report) for the full definition. After generation, deliver it [onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain) or via [HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http); HTTP receivers should [verify offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain). See [API Interactions: CRE reports over HTTP](/cre/guides/workflow/using-http-client#cre-reports-over-http). There are two encoding approaches depending on whether you have generated bindings for your contract. @@ -4744,7 +4751,7 @@ The CRE SDK provides an HTTP client that allows your workflows to interact with ## CRE reports over HTTP -A **CRE report** is a DON-signed package your workflow creates with `runtime.report()` (TypeScript) or `runtime.GenerateReport()` (Go). It contains your encoded payload, workflow metadata, and cryptographic signatures. +A **[CRE report](/cre/key-terms#report-cre-report)** is a DON-signed package your workflow creates with [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) (TypeScript) or [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) (Go). It bundles your encoded payload, workflow metadata, report context, and cryptographic signatures from the DON. See [Key Terms: Report](/cre/key-terms#report-cre-report) for the full definition, including how onchain delivery differs from HTTP. A typical secure integration uses two parties: @@ -15299,7 +15306,7 @@ This guide is for the **sender** side: a CRE workflow that **creates** a signed **What you'll learn:** -- How to use `SendReport` to submit reports via HTTP +- How to use [`SendReport()`](/cre/reference/sdk/http-client-go#sendrequestersendreport) to submit reports via HTTP - How to write transformation functions for different API formats - Best practices for report submission and deduplication @@ -15308,27 +15315,33 @@ This guide is for the **sender** side: a CRE workflow that **creates** a signed This guide covers HTTP submission. For submitting reports to smart contracts, see [Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain). +## What is a CRE report? + +A **[CRE report](/cre/key-terms#report-cre-report)** is the signed output your workflow DON produces when you call [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values). It packages your encoded data (the payload you computed in the callback), fixed workflow metadata, a report context (config digest and sequence number), and ECDSA signatures from DON nodes. + +This guide covers the **sender** path: create the report in your workflow, then POST it to an HTTP endpoint. The receiver must verify those signatures before trusting the data. See [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-go) on the receiver side. + ## Where this guide fits -| Question | Answer | -| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| What is the report? | Output of `runtime.GenerateReport()` after your workflow DON reaches consensus: encoded payload + metadata + signatures. | -| Where does it come from? | **Inside this workflow:** after your logic runs (fetch data, compute, encode). There is no separate "get report" step. | -| What does this guide cover? | Steps 3–4 below: `GenerateReport()`, then `SendReport()` to your API. Steps 1–2 (trigger and your callback logic) are prerequisites, not the focus here. | -| Who verifies it? | The **receiver:** your HTTP service or a separate CRE workflow. See [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-go). | +| Question | Answer | +| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| What is the report? | A [CRE report](/cre/key-terms#report-cre-report): output of [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) after DON consensus (encoded payload + metadata + signatures). | +| Where does it come from? | **Inside this workflow:** after your logic runs (fetch data, compute, encode). There is no separate "get report" step. | +| What does this guide cover? | Steps 3–4 below: [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values), then [`SendReport()`](/cre/reference/sdk/http-client-go#sendrequestersendreport) to your API. Steps 1–2 (trigger and your callback logic) are prerequisites, not the focus here. | +| Who verifies it? | The **receiver:** your HTTP service or a separate CRE workflow. See [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-go). | **Sender flow in one workflow execution:** 1. Trigger fires (cron, HTTP, …). 2. Your callback runs (API calls, encoding, etc.). -3. `runtime.GenerateReport()`: DON produces a signed `sdk.ReportResponse`. -4. `SendReport()`: format and POST to your URL. +3. [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values): DON produces a signed `sdk.ReportResponse`. +4. [`SendReport()`](/cre/reference/sdk/http-client-go#sendrequestersendreport): format and POST to your URL. ## Prerequisites - Familiarity with [making POST requests](/cre/guides/workflow/using-http-client/post-request) - Understanding of [generating reports](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) -- A generated report from `runtime.GenerateReport()` +- A generated report from [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) +## What is a CRE report? + +A **[CRE report](/cre/key-terms#report-cre-report)** is a DON-signed package another workflow (or system) created with [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values). You receive its bytes over HTTP (or another channel) as `rawReport`, `reportContext`, and `signatures`. Before you use the encoded payload, you must confirm the signatures match authorized DON signers on the Capability Registry. + +This guide is the **receiver** path: decode the POST body, call [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport), then read trusted metadata and `Body()`. See [Key Terms: Report](/cre/key-terms#report-cre-report) for how reports are created and delivered. + ## Where this guide fits -| Question | Answer | -| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| What is the report? | Same CRE report the **sender** created with `runtime.GenerateReport()`. See [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-go#where-this-guide-fits). | -| Where does it come from? | Another workflow (or system) already ran sender steps: logic → `GenerateReport()` → HTTP POST. You receive `rawReport`, `context`, and `signatures` in the request body. | -| What does this guide cover? | Step 3 below: `cre.ParseReport()` before you use `Body()` or take side effects. | -| Same workflow as the sender? | Often **no:** common pattern is Workflow A (publish) and Workflow B (ingest with HTTP trigger). | +| Question | Answer | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| What is the report? | Same [CRE report](/cre/key-terms#report-cre-report) the **sender** created with [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values). See [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-go#what-is-a-cre-report). | +| Where does it come from? | Another workflow (or system) already ran sender steps: logic → [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) → HTTP POST. You receive `rawReport`, `context`, and `signatures` in the request body. | +| What does this guide cover? | Step 3 below: [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) before you use `Body()` or take side effects. | +| Same workflow as the sender? | Often **no:** common pattern is Workflow A (publish) and Workflow B (ingest with HTTP trigger). | **Receiver flow:** 1. HTTP trigger (or your API) receives the POST payload. 2. Decode hex fields into bytes. -3. `cre.ParseReport()`: verify signatures and read metadata. +3. [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport): verify signatures and read metadata. 4. Use trusted `Body()` in your logic. Pair this guide with [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-go) on the sender side. This guide covers local simulation first, then the deploy example with `AuthorizedKeys`. @@ -16001,7 +16020,7 @@ Pair this guide with [Submitting Reports via HTTP](/cre/guides/workflow/using-ht ## What you'll learn - When to verify reports offchain vs relying on onchain forwarders -- How `cre.ParseReport()` validates signatures and reads metadata +- How [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) validates signatures and reads metadata - How to build a receiver workflow that accepts reports over HTTP - How to restrict verification to specific CRE environments or zones @@ -16013,12 +16032,12 @@ Pair this guide with [Submitting Reports via HTTP](/cre/guides/workflow/using-ht ## Onchain vs offchain verification -| Aspect | Offchain (`cre.ParseReport`) | Onchain (`KeystoneForwarder`) | -| -------------------- | ------------------------------------------------------------ | --------------------------------- | -| **Where it runs** | Inside your CRE workflow callback | In a smart contract transaction | -| **Signature check** | Local `ecrecover` on report hash | Contract logic onchain | -| **Signer allowlist** | Read from Capability Registry (`getDON`, `getNodesByP2PIds`) | Forwarder + registry | -| **Typical use** | HTTP APIs, webhooks, ingest workflows | Consumer contracts via `onReport` | +| Aspect | Offchain ([`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport)) | Onchain (`KeystoneForwarder`) | +| -------------------- | --------------------------------------------------------------------------- | --------------------------------- | +| **Where it runs** | Inside your CRE workflow callback | In a smart contract transaction | +| **Signature check** | Local `ecrecover` on report hash | Contract logic onchain | +| **Signer allowlist** | Read from Capability Registry (`getDON`, `getNodesByP2PIds`) | Forwarder + registry | +| **Typical use** | HTTP APIs, webhooks, ingest workflows | Consumer contracts via `onReport` | Offchain verification still uses **onchain data as a trust anchor**: the first time a DON is seen, the SDK reads the production Capability Registry on Ethereum Mainnet to learn `f` and authorized signer addresses. @@ -16034,7 +16053,7 @@ Default (`cre.ProductionEnvironment()`): 3. **Verify signatures**: compute `keccak256(keccak256(rawReport) || reportContext)`, recover signers, require **f+1** valid signatures from authorized nodes. 4. **Return a `*cre.Report`** with accessors for workflow ID, owner, execution ID, body, and more. -If verification fails, `cre.ParseReport()` returns an error (for example, `ErrUnknownSigner`, `ErrWrongSignatureCount`, or registry read failure). +If verification fails, [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) returns an error (for example, `ErrUnknownSigner`, `ErrWrongSignatureCount`, or registry read failure). ## Testing locally with simulation @@ -16046,7 +16065,7 @@ After you run the [submit guide complete example](/cre/guides/workflow/using-htt ### Minimal receiver for simulation -Use an empty HTTP trigger for sim. Set `SkipSignatureVerification: true` in staging config (or pass it to `ParseReportWithConfig`). The CLI delivers `--http-payload` file contents as `payload.Input` bytes. +Use an empty HTTP trigger for sim. Set `SkipSignatureVerification: true` in staging config (or pass it to [`ParseReportWithConfig`](/cre/reference/sdk/core-go#creparsereportwithconfig)). The CLI delivers `--http-payload` file contents as `payload.Input` bytes. `config.staging.json`: @@ -16135,10 +16154,10 @@ func main() { ### Sim wiring vs full verify -| Mode | Config | What it validates | -| ---------------------- | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------- | -| **Wiring / decode** | `SkipSignatureVerification: true` in `ParseReportWithConfig` | JSON + hex decode, metadata accessors, `Body()` | -| **Full crypto verify** | Default `cre.ParseReport()` (production registry) | Reports from a **deployed/production DON**. Sim-signed reports **fail** default verification. | +| Mode | Config | What it validates | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | +| **Wiring / decode** | `SkipSignatureVerification: true` in [`ParseReportWithConfig`](/cre/reference/sdk/core-go#creparsereportwithconfig) | JSON + hex decode, metadata accessors, `Body()` | +| **Full crypto verify** | Default [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) (production registry) | Reports from a **deployed/production DON**. Sim-signed reports **fail** default verification. | ```bash cre workflow simulate verify-report-receiver \ @@ -16150,8 +16169,8 @@ cre workflow simulate verify-report-receiver \ **Pass criteria** -- **Sim wiring:** `SkipSignatureVerification: true` in `ParseReportWithConfig`: logs show metadata and a successful handler return. -- **Full crypto verify:** default `ParseReport` with a **production-signed** report (not typical for sender-sim → receiver-sim alone). +- **Sim wiring:** `SkipSignatureVerification: true` in [`ParseReportWithConfig`](/cre/reference/sdk/core-go#creparsereportwithconfig): logs show metadata and a successful handler return. +- **Full crypto verify:** default [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) with a **production-signed** report (not typical for sender-sim → receiver-sim alone). `project.yaml` needs **`ethereum-mainnet` RPC** for default verify (registry reads). @@ -16255,7 +16274,7 @@ func main() { **What's happening:** 1. An external system POSTs hex-encoded `report`, `context`, and `signatures` to your HTTP trigger. -2. `cre.ParseReport()` verifies signatures against the production CRE registry. +2. [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) verifies signatures against the production CRE registry. 3. On success, you read metadata and `Body()` safely. @@ -16265,7 +16284,7 @@ func main() { ## Report payload format -Receivers need three JSON fields. The JSON key is `context` even though the SDK field is `ReportContext`: +When a sender POSTs a [CRE report](/cre/key-terms#report-cre-report) as JSON for offchain verification, receivers need three fields. The JSON key is `context` even though the SDK field is `ReportContext`: | JSON field | SDK field | Description | | ------------ | --------------- | --------------------------------------------------------------- | @@ -16288,7 +16307,7 @@ See [SDK Reference: Core: Report verification](/cre/reference/sdk/core-go#report func ParseReport(runtime Runtime, rawReport []byte, signatures [][]byte, reportContext []byte) (*Report, error) ``` -Parses and verifies a report against the production CRE environment. Use `ParseReportWithConfig` for custom environments or zones. +Parses and verifies a report against the production CRE environment. Use [`ParseReportWithConfig`](/cre/reference/sdk/core-go#creparsereportwithconfig) for custom environments or zones. ### `*cre.Report` accessors @@ -16329,7 +16348,7 @@ report, err := cre.ParseReportWithConfig(runtime, rawReport, sigs, reportContext This is a **different pattern** from the simulation testing use of `SkipSignatureVerification`. In testing, you skip verification permanently. Here, you parse the header first to inspect metadata (such as `WorkflowID()` or `DONID()` for filtering), then call `VerifySignatures` in a separate step — useful when you want to gate registry reads on workflow identity checks. -If you set `SkipSignatureVerification: true` in `ParseReportWithConfig`, parse the header first, then verify: +If you set `SkipSignatureVerification: true` in [`ParseReportWithConfig`](/cre/reference/sdk/core-go#creparsereportwithconfig), parse the header first, then verify: ```go report, err := cre.ParseReportWithConfig(runtime, rawReport, sigs, reportContext, cre.ReportParseConfig{ @@ -16348,7 +16367,7 @@ if err := report.VerifySignatures(runtime); err != nil { ## Best practices -1. **Verify before side effects**: Call `cre.ParseReport()` before writing to databases, chains, or external systems. +1. **Verify before side effects**: Call [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) before writing to databases, chains, or external systems. 2. **Permission on metadata**: After verification, check `WorkflowID()`, `WorkflowOwner()`, or `DONID()` match your expectations. 3. **Deduplicate by execution ID**: Use `ExecutionID()` or `keccak256(rawReport)` to reject replays (see [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-go#understanding-cachesettings-for-reports)). 4. **Do not skip signature verification in production** unless you have another trust path. @@ -16357,7 +16376,7 @@ if err := report.VerifySignatures(runtime); err != nil { **`ErrUnknownSigner` / `invalid signature` in sim with fresh webhook JSON** -- **Expected** when using default `ParseReport` on a **sim-signed** report: simulator DON keys do not match mainnet registry signers. +- **Expected** when using default [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) on a **sim-signed** report: simulator DON keys do not match mainnet registry signers. - For local wiring tests, use `SkipSignatureVerification: true`. For real crypto verify, use a **deployed sender** or production-signed reports. **`ErrUnknownSigner` (deployed)** diff --git a/src/content/cre/llms-full-ts.txt b/src/content/cre/llms-full-ts.txt index ee27231a3a5..3e58f8ca273 100644 --- a/src/content/cre/llms-full-ts.txt +++ b/src/content/cre/llms-full-ts.txt @@ -99,17 +99,18 @@ Learn more about [Consensus Computing in CRE](/cre/concepts/consensus-computing) ## Glossary: Building blocks -| Concept | One-liner | -| ------------------ | ----------------------------------------------------------------- | -| **Workflow** | Compiled WebAssembly (WASM) binary. | -| **Handler** | `handler(trigger, callback)` pair; the atom of execution. | -| **Trigger** | Event that starts an execution (cron, HTTP, EVM log, …). | -| **Callback** | Function that runs when its trigger fires; contains your logic. | -| **Runtime** | Object passed to a callback; used to invoke capabilities. | -| **Capability** | Decentralized microservice (chain read/write, HTTP Fetch, ...). | -| **Workflow DON** | Watches triggers and coordinates the workflow. | -| **Capability DON** | Executes a specific capability. | -| **Consensus** | BFT protocol that merges node results into one verifiable report. | +| Concept | One-liner | +| ------------------ | ---------------------------------------------------------------- | +| **Workflow** | Compiled WebAssembly (WASM) binary. | +| **Handler** | `handler(trigger, callback)` pair; the atom of execution. | +| **Trigger** | Event that starts an execution (cron, HTTP, EVM log, …). | +| **Callback** | Function that runs when its trigger fires; contains your logic. | +| **Runtime** | Object passed to a callback; used to invoke capabilities. | +| **Capability** | Decentralized microservice (chain read/write, HTTP Fetch, ...). | +| **Workflow DON** | Watches triggers and coordinates the workflow. | +| **Capability DON** | Executes a specific capability. | +| **Consensus** | BFT protocol that merges node results into one trusted outcome. | +| **Report** | DON-signed package from `runtime.report()` / `GenerateReport()`. | Full definitions live on **[Key Terms and Concepts](/cre/key-terms)**. @@ -149,7 +150,7 @@ Jump to what you need: # Key Terms and Concepts Source: https://docs.chain.link/cre/key-terms -Last Updated: 2025-11-04 +Last Updated: 2026-05-20 This page defines the fundamental terms and concepts for the Chainlink Runtime Environment (CRE). @@ -216,6 +217,12 @@ Short-lived objects passed to your callback function during a specific execution Learn more about [Consensus and Aggregation](/cre/reference/sdk/consensus). +### Report (CRE report) + +A cryptographically signed package your workflow [DON](/cre/key-terms#decentralized-oracle-network-don) produces after your callback encodes its result: payload bytes, report context, and DON signatures. Create it with [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) (TypeScript) or [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) (Go). + +Deliver onchain with [`writeReport()`](/cre/reference/sdk/evm-client-ts#writereport) / [`WriteReport()`](/cre/reference/sdk/evm-client-go#writereport), or offchain with [`sendReport()`](/cre/reference/sdk/http-client-ts#using-sendreport) / [`SendReport()`](/cre/reference/sdk/http-client-go#sendrequestersendreport). Receivers verify with [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) or [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport). See [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http) and [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain). + ### SDK Clients: `EVMClient` & `HTTPClient` The primary SDK clients you use inside a callback to interact with capabilities. For example, you use an EVM client to read from a smart contract and an HTTP client to make offchain API requests. @@ -4053,7 +4060,7 @@ The CRE SDK provides an HTTP client that allows your workflows to interact with ## CRE reports over HTTP -A **CRE report** is a DON-signed package your workflow creates with `runtime.report()` (TypeScript) or `runtime.GenerateReport()` (Go). It contains your encoded payload, workflow metadata, and cryptographic signatures. +A **[CRE report](/cre/key-terms#report-cre-report)** is a DON-signed package your workflow creates with [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) (TypeScript) or [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) (Go). It bundles your encoded payload, workflow metadata, report context, and cryptographic signatures from the DON. See [Key Terms: Report](/cre/key-terms#report-cre-report) for the full definition, including how onchain delivery differs from HTTP. A typical secure integration uses two parties: @@ -15004,7 +15011,7 @@ This guide is for the **sender** side: a CRE workflow that **creates** a signed **What you'll learn:** -- How to use `sendReport()` to submit reports via HTTP +- How to use [`sendReport()`](/cre/reference/sdk/http-client-ts#using-sendreport) to submit reports via HTTP - How to write transformation functions for different API formats - Best practices for report submission and deduplication @@ -15013,26 +15020,32 @@ This guide is for the **sender** side: a CRE workflow that **creates** a signed This guide covers HTTP submission. For submitting reports to smart contracts, see [Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/overview-ts). +## What is a CRE report? + +A **[CRE report](/cre/key-terms#report-cre-report)** is the signed output your workflow DON produces when you call [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime). It packages your encoded data (the payload you computed in the callback), fixed workflow metadata, a report context (config digest and sequence number), and ECDSA signatures from DON nodes. + +This guide covers the **sender** path: create the report in your workflow, then POST it to an HTTP endpoint. The receiver must verify those signatures before trusting the data. See [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts) on the receiver side. + ## Where this guide fits -| Question | Answer | -| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| What is the report? | Output of `runtime.report()` after your workflow DON reaches consensus: encoded payload + metadata + signatures. | -| Where does it come from? | **Inside this workflow:** after your logic runs (fetch data, compute, encode). There is no separate "get report" step. | -| What does this guide cover? | Steps 3–4 below: `runtime.report()`, then `sendReport()` to your API. Steps 1–2 (trigger and your callback logic) are prerequisites, not the focus here. | -| Who verifies it? | The **receiver:** your HTTP service or a separate CRE workflow. See [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts). | +| Question | Answer | +| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| What is the report? | A [CRE report](/cre/key-terms#report-cre-report): output of [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) after DON consensus (encoded payload + metadata + signatures). | +| Where does it come from? | **Inside this workflow:** after your logic runs (fetch data, compute, encode). There is no separate "get report" step. | +| What does this guide cover? | Steps 3–4 below: [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime), then [`sendReport()`](/cre/reference/sdk/http-client-ts#using-sendreport) to your API. Steps 1–2 (trigger and your callback logic) are prerequisites, not the focus here. | +| Who verifies it? | The **receiver:** your HTTP service or a separate CRE workflow. See [Verifying CRE Reports Offchain](/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts). | **Sender flow in one workflow execution:** 1. Trigger fires (cron, HTTP, …). 2. Your callback runs (API calls, encoding, etc.). -3. `runtime.report()`: DON produces a signed `ReportResponse`. -4. `sendReport()`: format and POST to your URL. +3. [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime): DON produces a signed `ReportResponse`. +4. [`sendReport()`](/cre/reference/sdk/http-client-ts#using-sendreport): format and POST to your URL. ## Prerequisites - Familiarity with [making POST requests](/cre/guides/workflow/using-http-client/post-request) -- Familiarity with `runtime.report()` (covered [below](#generating-reports-for-http-submission)) +- Familiarity with [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) (covered [below](#generating-reports-for-http-submission)) - **`viem`** as a direct dependency in the workflow `package.json` (for ABI encoding in examples) - Protobuf HTTP/report types from **`@chainlink/cre-sdk/pb`** (`SDK_PB.ReportResponse`, `HTTP_CLIENT_PB.RequestJson`), not the main `@chainlink/cre-sdk` entry @@ -15095,7 +15108,7 @@ const submitReport = (sendRequester: HTTPSendRequester, report: Report): { succe **What's happening here:** 1. `formatReportSimple` transforms the report into an HTTP request that your API understands -2. `sendRequester.sendReport()` calls your transformation function and sends the request +2. [`sendRequester.sendReport()`](/cre/reference/sdk/http-client-ts#using-sendreport) calls your transformation function and sends the request 3. The SDK handles consensus and returns the result The rest of this guide explains how this works and shows different formatting patterns for various API requirements. @@ -15104,7 +15117,7 @@ The rest of this guide explains how this works and shows different formatting pa ### The report structure -When you call `runtime.report()`, the SDK creates a `ReportResponse` containing: +After [consensus](/cre/key-terms#consensus), [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) returns a `ReportResponse`: the wire-format view of a [CRE report](/cre/key-terms#report-cre-report). It contains: ```typescript interface ReportResponse { @@ -15133,7 +15146,7 @@ Your transformation function tells the SDK how to format the report for your API **The SDK calls this function internally:** -1. You pass your transformation function to `sendReport()` +1. You pass your transformation function to [`sendReport()`](/cre/reference/sdk/http-client-ts#using-sendreport) 2. The SDK calls it with the generated `ReportResponse` 3. Your function returns a `RequestJson` formatted for your API 4. The SDK sends the request and handles consensus @@ -15383,7 +15396,7 @@ This approach is reliable because the `rawReport` is identical across all nodes ## Generating reports for HTTP submission -Before you can submit a report via HTTP, you need to generate it using `runtime.report()`. This creates a cryptographically signed report from your encoded data. +Before you can submit a report via HTTP, you need to generate it using [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime). This creates a cryptographically signed report from your encoded data. **Basic pattern:** @@ -15407,11 +15420,11 @@ const report = runtime // Step 3: Submit via HTTP (covered in next section) ``` -The `runtime.report()` method works the same way whether you're encoding a single value or a struct—just use Viem's `encodeAbiParameters()` with the appropriate ABI types. For detailed examples on encoding single values, structs, and complex types, see the [Writing Data Onchain](/cre/guides/workflow/using-evm-client/onchain-write/writing-data-onchain) guide. +The [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) method works the same way whether you're encoding a single value or a struct—just use Viem's `encodeAbiParameters()` with the appropriate ABI types. For detailed examples on encoding single values, structs, and complex types, see the [Writing Data Onchain](/cre/guides/workflow/using-evm-client/onchain-write/writing-data-onchain) guide. ## Using `sendReport()` (recommended approach) -Use the high-level `httpClient.sendRequest()` pattern with `sendRequester.sendReport()`: +Use the high-level `httpClient.sendRequest()` pattern with [`sendRequester.sendReport()`](/cre/reference/sdk/http-client-ts#using-sendreport): ```typescript import { @@ -15662,7 +15675,7 @@ const onCronTrigger = (runtime: Runtime): MyResult => { ## Troubleshooting @@ -15703,11 +15716,11 @@ This guide is for the **receiver** side: you already received a CRE report packa When a workflow delivers results via HTTP (or another offchain channel), nothing onchain automatically validates the report. **You must verify signatures before trusting the data.** -The CRE SDK provides `Report.parse()` to do this inside a workflow. Verification runs **offchain** in your callback: signatures are checked with local cryptography, while authorized signer addresses are loaded via **read-only calls to the onchain Capability Registry** (default: Ethereum Mainnet). Results are cached per DON. +The CRE SDK provides [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) to do this inside a workflow. Verification runs **offchain** in your callback: signatures are checked with local cryptography, while authorized signer addresses are loaded via **read-only calls to the onchain Capability Registry** (default: Ethereum Mainnet). Results are cached per DON. @@ -15715,20 +15728,26 @@ The CRE SDK provides `Report.parse()` to do this inside a workflow. Verification When you submit reports onchain through the `KeystoneForwarder`, the forwarder contract verifies signatures before calling your consumer's `onReport`. This guide covers **offchain** verification for HTTP and custom ingest paths. See [Submitting Reports Onchain](/cre/guides/workflow/using-evm-client/onchain-write/submitting-reports-onchain). +## What is a CRE report? + +A **[CRE report](/cre/key-terms#report-cre-report)** is a DON-signed package another workflow (or system) created with [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime). You receive its bytes over HTTP (or another channel) as `rawReport`, `reportContext`, and `signatures`. Before you use the encoded payload, you must confirm the signatures match authorized DON signers on the Capability Registry. + +This guide is the **receiver** path: decode the POST body, call [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification), then read trusted metadata and `body()`. See [Key Terms: Report](/cre/key-terms#report-cre-report) for how reports are created and delivered. + ## Where this guide fits -| Question | Answer | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| What is the report? | Same CRE report the **sender** created with `runtime.report()`. See [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts#where-this-guide-fits). | -| Where does it come from? | Another workflow (or system) already ran sender steps: logic → `runtime.report()` → HTTP POST. You receive `rawReport`, `context`, and `signatures` in the request body. | -| What does this guide cover? | Step 3 below: `Report.parse()` before you use `body()` or take side effects. | -| Same workflow as the sender? | Often **no:** common pattern is Workflow A (publish) and Workflow B (ingest with HTTP trigger). | +| Question | Answer | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| What is the report? | Same [CRE report](/cre/key-terms#report-cre-report) the **sender** created with [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime). See [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts#what-is-a-cre-report). | +| Where does it come from? | Another workflow (or system) already ran sender steps: logic → [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) → HTTP POST. You receive `rawReport`, `context`, and `signatures` in the request body. | +| What does this guide cover? | Step 3 below: [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) before you use `body()` or take side effects. | +| Same workflow as the sender? | Often **no:** common pattern is Workflow A (publish) and Workflow B (ingest with HTTP trigger). | **Receiver flow:** 1. HTTP trigger (or your API) receives the POST payload. 2. Decode hex fields into bytes. -3. `Report.parse()`: verify signatures and read metadata. +3. [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification): verify signatures and read metadata. 4. Use trusted `body()` in your logic. Pair this guide with [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts) on the sender side. This guide covers local simulation first, then the deploy example with `authorizedKeys`. @@ -15736,7 +15755,7 @@ Pair this guide with [Submitting Reports via HTTP](/cre/guides/workflow/using-ht ## What you'll learn - When to verify reports offchain vs relying on onchain forwarders -- How `Report.parse()` validates signatures and reads metadata +- How [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) validates signatures and reads metadata - How to build a receiver workflow that accepts reports over HTTP - How to restrict verification to specific CRE environments or zones @@ -15748,12 +15767,12 @@ Pair this guide with [Submitting Reports via HTTP](/cre/guides/workflow/using-ht ## Onchain vs offchain verification -| Aspect | Offchain (`Report.parse`) | Onchain (`KeystoneForwarder`) | -| -------------------- | ------------------------------------------------------------ | --------------------------------- | -| **Where it runs** | Inside your CRE workflow callback | In a smart contract transaction | -| **Signature check** | Local `ecrecover` on report hash | Contract logic onchain | -| **Signer allowlist** | Read from Capability Registry (`getDON`, `getNodesByP2PIds`) | Forwarder + registry | -| **Typical use** | HTTP APIs, webhooks, ingest workflows | Consumer contracts via `onReport` | +| Aspect | Offchain ([`Report.parse()`](/cre/reference/sdk/core-ts#report-verification)) | Onchain (`KeystoneForwarder`) | +| -------------------- | ----------------------------------------------------------------------------- | --------------------------------- | +| **Where it runs** | Inside your CRE workflow callback | In a smart contract transaction | +| **Signature check** | Local `ecrecover` on report hash | Contract logic onchain | +| **Signer allowlist** | Read from Capability Registry (`getDON`, `getNodesByP2PIds`) | Forwarder + registry | +| **Typical use** | HTTP APIs, webhooks, ingest workflows | Consumer contracts via `onReport` | Offchain verification still uses **onchain data as a trust anchor**: the first time a DON is seen, the SDK reads the production Capability Registry on Ethereum Mainnet to learn `f` and authorized signer addresses. @@ -15769,7 +15788,7 @@ Default (`productionEnvironment()`): 3. **Verify signatures**: compute `keccak256(keccak256(rawReport) || reportContext)`, recover signers, require **f+1** valid signatures from authorized nodes. 4. **Return a `Report` object** with accessors for workflow ID, owner, execution ID, body, and more. -If verification fails, `Report.parse()` throws (for example, unknown signer, insufficient signatures, or registry read failure). +If verification fails, [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) throws (for example, unknown signer, insufficient signatures, or registry read failure). ## Testing locally with simulation @@ -15781,14 +15800,14 @@ After you run the [submit guide complete example](/cre/guides/workflow/using-htt ### Sim wiring vs full verify -| Mode | Config | What it validates | -| ---------------------- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Wiring / decode** | `skipSignatureVerification: true` in staging config | JSON + hex decode, `workflowId()`, `executionId()`, `donId()`, `body()` | -| **Full crypto verify** | Default `Report.parse()` (production registry) | Reports from a **deployed/production DON**. Sim-signed reports **fail** default verification: simulation uses local DON keys; `Report.parse()` checks the mainnet Capability Registry. | +| Mode | Config | What it validates | +| ---------------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Wiring / decode** | `skipSignatureVerification: true` in staging config | JSON + hex decode, `workflowId()`, `executionId()`, `donId()`, `body()` | +| **Full crypto verify** | Default [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) (production registry) | Reports from a **deployed/production DON**. Sim-signed reports **fail** default verification: simulation uses local DON keys; [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) checks the mainnet Capability Registry. | ### Minimal receiver for simulation -Use an **empty HTTP trigger config** for sim (add `authorizedKeys` before deploy). Call `Report.parse()` from your handler with the `runtime` parameter. The CLI delivers `--http-payload` file contents as `payload.input` bytes. +Use an **empty HTTP trigger config** for sim (add `authorizedKeys` before deploy). Call [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) from your handler with the `runtime` parameter. The CLI delivers `--http-payload` file contents as `payload.input` bytes. `config.staging.json`: @@ -15949,7 +15968,7 @@ export const initWorkflow = (config: Config, _secretsProvider: SecretsProvider) **What's happening:** 1. An external system POSTs hex-encoded `report`, `context`, and `signatures` to your HTTP trigger. -2. `Report.parse()` verifies signatures against the production CRE registry. +2. [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) verifies signatures against the production CRE registry. 3. On success, you read metadata and `body()` safely. @@ -15959,7 +15978,7 @@ export const initWorkflow = (config: Config, _secretsProvider: SecretsProvider) ## Report payload format -Receivers need three JSON fields (plus optional metadata your API may add). The JSON key is `context` even though the SDK field is `reportContext`: +When a sender POSTs a [CRE report](/cre/key-terms#report-cre-report) as JSON for offchain verification, receivers need three fields (plus optional metadata your API may add). The JSON key is `context` even though the SDK field is `reportContext`: | JSON field | SDK field | Description | | ------------ | --------------- | --------------------------------------------------------------- | @@ -16018,17 +16037,17 @@ const config: ReportParseConfig = { } ``` -| Option | Description | -| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `acceptedEnvironments` | Registry environments to check (defaults to production) | -| `acceptedZones` | Restrict to specific DON IDs within an environment | -| `skipSignatureVerification` | Parse metadata only, without registry reads or signature checks. Use only for testing or when another layer verifies signatures. There is no separate `verifySignatures()` on `Report` in TypeScript; call `Report.parse()` without this flag for production verification. | +| Option | Description | +| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `acceptedEnvironments` | Registry environments to check (defaults to production) | +| `acceptedZones` | Restrict to specific DON IDs within an environment | +| `skipSignatureVerification` | Parse metadata only, without registry reads or signature checks. Use only for testing or when another layer verifies signatures. There is no separate `verifySignatures()` on `Report` in TypeScript; call [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) without this flag for production verification. | Most workflows should use the default config (production environment only). ## Best practices -1. **Verify before side effects**: Call `Report.parse()` before writing to databases, chains, or external systems. +1. **Verify before side effects**: Call [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) before writing to databases, chains, or external systems. 2. **Permission on metadata**: After verification, check `workflowId()`, `workflowOwner()`, or `donId()` match your expectations. 3. **Deduplicate by execution ID**: Use `executionId()` or `keccak256(rawReport)` to reject replays (see [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts#understanding-cachesettings-for-reports)). 4. **Do not skip signature verification in production** unless you have another trust path. @@ -16037,12 +16056,12 @@ Most workflows should use the default config (production environment only). **Empty error after verify sim** -- `Report.parse()` may throw an **`AggregateError`** of multiple `invalid signature` errors. **`AggregateError.message` is often empty**, so the CLI prints `Execution resulted in an error being returned:` with nothing after the colon. +- [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) may throw an **`AggregateError`** of multiple `invalid signature` errors. **`AggregateError.message` is often empty**, so the CLI prints `Execution resulted in an error being returned:` with nothing after the colon. - Format errors in your handler before rethrowing (see the simulation example above). **`invalid signature` / `unknown signer` in sim with fresh webhook JSON** -- **Expected** when using default `Report.parse()` on a **sim-signed** report: simulator DON keys do not match mainnet registry signers. +- **Expected** when using default [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) on a **sim-signed** report: simulator DON keys do not match mainnet registry signers. - For local wiring tests, set `skipSignatureVerification: true`. For real crypto verify, use a **deployed sender** or production-signed reports. **`invalid signature` / `unknown signer` (deployed)** From 86cec49023729507d6a30db7a09b2b293c752bf5 Mon Sep 17 00:00:00 2001 From: devin distefano Date: Tue, 9 Jun 2026 11:28:59 -0500 Subject: [PATCH 7/9] restructure (untested) --- .../verifying-reports-offchain-go.mdx | 168 +++++++++++++++--- .../verifying-reports-offchain-ts.mdx | 152 +++++++++++++--- src/content/cre/llms-full-go.txt | 168 +++++++++++++++--- src/content/cre/llms-full-ts.txt | 152 +++++++++++++--- 4 files changed, 524 insertions(+), 116 deletions(-) diff --git a/src/content/cre/guides/workflow/using-http-client/verifying-reports-offchain-go.mdx b/src/content/cre/guides/workflow/using-http-client/verifying-reports-offchain-go.mdx index d87b1fffa39..2710d920dc5 100644 --- a/src/content/cre/guides/workflow/using-http-client/verifying-reports-offchain-go.mdx +++ b/src/content/cre/guides/workflow/using-http-client/verifying-reports-offchain-go.mdx @@ -16,47 +16,34 @@ This guide is for the **receiver** side: you already received a CRE report packa When a workflow delivers results via HTTP (or another offchain channel), nothing onchain automatically validates the report. **You must verify signatures before trusting the data.** -The CRE SDK provides [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) to do this inside a workflow. Verification runs offchain in your callback: signatures are checked with local cryptography, while authorized signer addresses are loaded via **read-only calls to the onchain Capability Registry** (default: Ethereum Mainnet). Results are cached per DON. +There are two ways to verify, depending on who receives the POST: -{/* prettier-ignore */} - +| My receiver is... | Use | +| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **My own API or server** (Go HTTP server, etc.) | [Verifying outside CRE](#verifying-outside-cre) — standard crypto libraries, no CRE SDK needed | +| **A CRE workflow** with an HTTP trigger | [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) — see [Testing locally with simulation](#testing-locally-with-simulation) and the [deploy example](#complete-example-http-receiver-workflow-deploy) | -{/* prettier-ignore */} - +Both paths run the same cryptographic checks against the same onchain Capability Registry. The difference is whether you use the CRE SDK's helper (which requires `runtime`) or implement it yourself with standard libraries. ## What is a CRE report? A **[CRE report](/cre/key-terms#report-cre-report)** is a DON-signed package another workflow (or system) created with [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values). You receive its bytes over HTTP (or another channel) as `rawReport`, `reportContext`, and `signatures`. Before you use the encoded payload, you must confirm the signatures match authorized DON signers on the Capability Registry. -This guide is the **receiver** path: decode the POST body, call [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport), then read trusted metadata and `Body()`. See [Key Terms: Report](/cre/key-terms#report-cre-report) for how reports are created and delivered. - -## Where this guide fits +See [Key Terms: Report](/cre/key-terms#report-cre-report) for how reports are created and delivered. Pair this guide with [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-go) on the sender side. -| Question | Answer | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| What is the report? | Same [CRE report](/cre/key-terms#report-cre-report) the **sender** created with [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values). See [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-go#what-is-a-cre-report). | -| Where does it come from? | Another workflow (or system) already ran sender steps: logic → [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) → HTTP POST. You receive `rawReport`, `context`, and `signatures` in the request body. | -| What does this guide cover? | Step 3 below: [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) before you use `Body()` or take side effects. | -| Same workflow as the sender? | Often **no:** common pattern is Workflow A (publish) and Workflow B (ingest with HTTP trigger). | +**Receiver flow (both paths):** -**Receiver flow:** - -1. HTTP trigger (or your API) receives the POST payload. +1. Your endpoint (API or CRE HTTP trigger) receives the POST payload. 2. Decode hex fields into bytes. -3. [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport): verify signatures and read metadata. -4. Use trusted `Body()` in your logic. - -Pair this guide with [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-go) on the sender side. This guide covers local simulation first, then the deploy example with `AuthorizedKeys`. +3. Verify signatures against the Capability Registry. +4. Use the trusted payload body in your logic. ## What you'll learn - When to verify reports offchain vs relying on onchain forwarders -- How [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) validates signatures and reads metadata -- How to build a receiver workflow that accepts reports over HTTP +- How [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) validates signatures and reads metadata (inside a CRE workflow) +- How to build a CRE receiver workflow that accepts reports over HTTP +- How to verify reports in your own API server without a CRE receiver workflow - How to restrict verification to specific CRE environments or zones ## Prerequisites @@ -72,7 +59,7 @@ Pair this guide with [Submitting Reports via HTTP](/cre/guides/workflow/using-ht | **Where it runs** | Inside your CRE workflow callback | In a smart contract transaction | | **Signature check** | Local `ecrecover` on report hash | Contract logic onchain | | **Signer allowlist** | Read from Capability Registry (`getDON`, `getNodesByP2PIds`) | Forwarder + registry | -| **Typical use** | HTTP APIs, webhooks, ingest workflows | Consumer contracts via `onReport` | +| **Typical use** | CRE receiver workflows with an HTTP trigger | Consumer contracts via `onReport` | Offchain verification still uses **onchain data as a trust anchor**: the first time a DON is seen, the SDK reads the production Capability Registry on Ethereum Mainnet to learn `f` and authorized signer addresses. @@ -90,7 +77,130 @@ Default (`cre.ProductionEnvironment()`): If verification fails, [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) returns an error (for example, `ErrUnknownSigner`, `ErrWrongSignatureCount`, or registry read failure). -## Testing locally with simulation +## Verifying outside CRE + +[`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) requires the CRE `runtime` object, so it can only run inside a CRE workflow callback. If your API server receives reports directly, you can implement the same verification steps with standard libraries — no CRE SDK required. + +**What you need:** + +- An Ethereum Mainnet RPC endpoint (for Capability Registry reads) +- `github.com/ethereum/go-ethereum/crypto` for `Keccak256` and signature recovery + +**Verification steps:** + +1. Decode the hex JSON fields into bytes. +2. Parse the **109-byte metadata header** from `rawReport` to extract `donId`, `workflowId`, `workflowOwner`, and `body`. +3. Compute the message hash: `keccak256(keccak256(rawReport) || reportContext)`. +4. Fetch the DON's fault tolerance `f` and authorized signer addresses from the **Capability Registry** on Ethereum Mainnet (`0x76c9cf548b4179F8901cda1f8623568b58215E62`): call `getDON(donId)` then `getNodesByP2PIds(p2pIds)`. +5. For each signature (65 bytes), recover the signing address. Require at least **f+1** addresses from the authorized signer set. + +The following sketch shows the crypto steps. The Capability Registry call (step 4) follows the same `getDON` → `getNodesByP2PIds` ABI logic used in the CRE SDK; see [`report_verification.go` in cre-sdk-go](https://github.com/smartcontractkit/cre-sdk-go) for the exact call and decoding logic. + +```go +package main + +import ( + "encoding/binary" + "encoding/hex" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" +) + +const ( + reportHeaderLength = 109 + capabilityRegistry = "0x76c9cf548b4179F8901cda1f8623568b58215E62" +) + +// parseHeader extracts key fields from the 109-byte rawReport metadata header. +func parseHeader(rawReport []byte) (donId uint32, workflowId string, body []byte, err error) { + if len(rawReport) < reportHeaderLength { + return 0, "", nil, fmt.Errorf("rawReport too short: need %d bytes, got %d", reportHeaderLength, len(rawReport)) + } + donId = binary.BigEndian.Uint32(rawReport[37:41]) + workflowId = hex.EncodeToString(rawReport[45:77]) + body = rawReport[reportHeaderLength:] + return +} + +// reportHash computes keccak256(keccak256(rawReport) || reportContext). +func reportHash(rawReport, reportContext []byte) []byte { + inner := crypto.Keccak256(rawReport) + return crypto.Keccak256(append(inner, reportContext...)) +} + +// fetchSigners calls the Capability Registry on Ethereum Mainnet to get +// the fault tolerance f and authorized signer addresses for the given DON. +// Use go-ethereum's ethclient and ABI encoding. +// See report_verification.go in cre-sdk-go for the exact call and decoding logic. +func fetchSigners(client *ethclient.Client, donId uint32) (f int, signers map[common.Address]bool, err error) { + // TODO: implement using your Ethereum Mainnet RPC + // 1. ABI-encode getDON(donId), call capabilityRegistry, decode f and nodeP2PIds + // 2. ABI-encode getNodesByP2PIds(nodeP2PIds), call capabilityRegistry, decode signer addresses + return 0, nil, fmt.Errorf("implement: call getDON then getNodesByP2PIds on the Capability Registry") +} + +// verifyReport checks that ≥ f+1 signatures are from authorized DON signers. +func verifyReport(rawReport []byte, signatures [][]byte, reportContext []byte, client *ethclient.Client) error { + donId, _, _, err := parseHeader(rawReport) + if err != nil { + return err + } + f, signers, err := fetchSigners(client, donId) + if err != nil { + return err + } + hash := reportHash(rawReport, reportContext) + required := f + 1 + valid := 0 + + for _, sig := range signatures { + if len(sig) != 65 { + continue + } + norm := make([]byte, 65) + copy(norm, sig) + if norm[64] >= 27 { + norm[64] -= 27 // normalize recovery byte + } + pubKey, err := crypto.SigToPub(hash, norm) + if err != nil { + continue + } + addr := crypto.PubkeyToAddress(*pubKey) + if signers[addr] { + valid++ + } + if valid >= required { + return nil + } + } + return fmt.Errorf("insufficient valid signatures: %d/%d", valid, required) +} +``` + +{/* prettier-ignore */} + + +## CRE receiver workflow + +Use this path if you want the receiver itself to be a CRE workflow (with an HTTP trigger). The CRE SDK's [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) handles registry reads and caching automatically. + +{/* prettier-ignore */} + + +{/* prettier-ignore */} + + +### Testing locally with simulation After you run the [submit guide complete example](/cre/guides/workflow/using-http-client/submitting-reports-http-go#complete-working-example) and copy JSON from webhook.site, use this section to exercise a receiver workflow in simulation. diff --git a/src/content/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts.mdx b/src/content/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts.mdx index ed6a799d911..68bf1ce1f33 100644 --- a/src/content/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts.mdx +++ b/src/content/cre/guides/workflow/using-http-client/verifying-reports-offchain-ts.mdx @@ -16,47 +16,34 @@ This guide is for the **receiver** side: you already received a CRE report packa When a workflow delivers results via HTTP (or another offchain channel), nothing onchain automatically validates the report. **You must verify signatures before trusting the data.** -The CRE SDK provides [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) to do this inside a workflow. Verification runs **offchain** in your callback: signatures are checked with local cryptography, while authorized signer addresses are loaded via **read-only calls to the onchain Capability Registry** (default: Ethereum Mainnet). Results are cached per DON. +There are two ways to verify, depending on who receives the POST: -{/* prettier-ignore */} - +| My receiver is... | Use | +| ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **My own API or server** (Express, FastAPI, Go server, etc.) | [Verifying outside CRE](#verifying-outside-cre) — standard crypto libraries, no CRE SDK needed | +| **A CRE workflow** with an HTTP trigger | [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) — see [Testing locally with simulation](#testing-locally-with-simulation) and the [deploy example](#complete-example-http-receiver-workflow-deploy) | -{/* prettier-ignore */} - +Both paths run the same cryptographic checks against the same onchain Capability Registry. The difference is whether you use the CRE SDK's helper (which requires `runtime`) or implement it yourself with standard libraries. ## What is a CRE report? A **[CRE report](/cre/key-terms#report-cre-report)** is a DON-signed package another workflow (or system) created with [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime). You receive its bytes over HTTP (or another channel) as `rawReport`, `reportContext`, and `signatures`. Before you use the encoded payload, you must confirm the signatures match authorized DON signers on the Capability Registry. -This guide is the **receiver** path: decode the POST body, call [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification), then read trusted metadata and `body()`. See [Key Terms: Report](/cre/key-terms#report-cre-report) for how reports are created and delivered. - -## Where this guide fits +See [Key Terms: Report](/cre/key-terms#report-cre-report) for how reports are created and delivered. Pair this guide with [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts) on the sender side. -| Question | Answer | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| What is the report? | Same [CRE report](/cre/key-terms#report-cre-report) the **sender** created with [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime). See [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts#what-is-a-cre-report). | -| Where does it come from? | Another workflow (or system) already ran sender steps: logic → [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) → HTTP POST. You receive `rawReport`, `context`, and `signatures` in the request body. | -| What does this guide cover? | Step 3 below: [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) before you use `body()` or take side effects. | -| Same workflow as the sender? | Often **no:** common pattern is Workflow A (publish) and Workflow B (ingest with HTTP trigger). | +**Receiver flow (both paths):** -**Receiver flow:** - -1. HTTP trigger (or your API) receives the POST payload. +1. Your endpoint (API or CRE HTTP trigger) receives the POST payload. 2. Decode hex fields into bytes. -3. [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification): verify signatures and read metadata. -4. Use trusted `body()` in your logic. - -Pair this guide with [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts) on the sender side. This guide covers local simulation first, then the deploy example with `authorizedKeys`. +3. Verify signatures against the Capability Registry. +4. Use the trusted payload body in your logic. ## What you'll learn - When to verify reports offchain vs relying on onchain forwarders -- How [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) validates signatures and reads metadata -- How to build a receiver workflow that accepts reports over HTTP +- How [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) validates signatures and reads metadata (inside a CRE workflow) +- How to build a CRE receiver workflow that accepts reports over HTTP +- How to verify reports in your own API server without a CRE receiver workflow - How to restrict verification to specific CRE environments or zones ## Prerequisites @@ -72,7 +59,7 @@ Pair this guide with [Submitting Reports via HTTP](/cre/guides/workflow/using-ht | **Where it runs** | Inside your CRE workflow callback | In a smart contract transaction | | **Signature check** | Local `ecrecover` on report hash | Contract logic onchain | | **Signer allowlist** | Read from Capability Registry (`getDON`, `getNodesByP2PIds`) | Forwarder + registry | -| **Typical use** | HTTP APIs, webhooks, ingest workflows | Consumer contracts via `onReport` | +| **Typical use** | CRE receiver workflows with an HTTP trigger | Consumer contracts via `onReport` | Offchain verification still uses **onchain data as a trust anchor**: the first time a DON is seen, the SDK reads the production Capability Registry on Ethereum Mainnet to learn `f` and authorized signer addresses. @@ -90,7 +77,114 @@ Default (`productionEnvironment()`): If verification fails, [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) throws (for example, unknown signer, insufficient signatures, or registry read failure). -## Testing locally with simulation +## Verifying outside CRE + +[`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) requires the CRE `runtime` object, so it can only run inside a CRE workflow callback. If your API server receives reports directly, you can implement the same verification steps with standard libraries — no CRE SDK required. + +**What you need:** + +- An Ethereum Mainnet RPC endpoint (for Capability Registry reads) +- `viem` for the cryptographic primitives (`keccak256`, `recoverAddress`) + +**Verification steps:** + +1. Decode the hex JSON fields into bytes. +2. Parse the **109-byte metadata header** from `rawReport` to extract `donId`, `workflowId`, `workflowOwner`, and `body`. +3. Compute the message hash: `keccak256(keccak256(rawReport) || reportContext)`. +4. Fetch the DON's fault tolerance `f` and authorized signer addresses from the **Capability Registry** on Ethereum Mainnet (`0x76c9cf548b4179F8901cda1f8623568b58215E62`): call `getDON(donId)` then `getNodesByP2PIds(p2pIds)`. +5. For each signature (65 bytes), `ecrecover` the signing address. Require at least **f+1** addresses from the authorized signer set. + +The following sketch shows the crypto steps. The Capability Registry call (step 4) follows the same `getDON` → `getNodesByP2PIds` ABI logic used in the CRE SDK; see [`report.ts` in cre-sdk-typescript](https://github.com/smartcontractkit/cre-sdk-typescript) for the exact ABI encoding and decoding. + +```typescript +import { keccak256, concatHex, toHex, recoverAddress } from "viem" + +const REPORT_HEADER_LENGTH = 109 + +/** Parse key fields from the 109-byte rawReport metadata header. */ +function parseHeader(rawReport: Uint8Array) { + if (rawReport.length < REPORT_HEADER_LENGTH) { + throw new Error(`rawReport too short: need ${REPORT_HEADER_LENGTH} bytes, got ${rawReport.length}`) + } + const view = new DataView(rawReport.buffer, rawReport.byteOffset) + return { + donId: view.getUint32(37, false), // bytes 37–40 + workflowId: toHex(rawReport.slice(45, 77)), // bytes 45–76 + workflowOwner: toHex(rawReport.slice(87, 107)), // bytes 87–106 + body: rawReport.slice(REPORT_HEADER_LENGTH), // bytes 109+ + } +} + +/** keccak256(keccak256(rawReport) || reportContext) */ +function reportHash(rawReport: Uint8Array, reportContext: Uint8Array): `0x${string}` { + return keccak256(concatHex([keccak256(toHex(rawReport)), toHex(reportContext)])) +} + +/** + * Fetch fault tolerance f and authorized signer addresses from the Capability Registry. + * Calls getDON(donId) → getNodesByP2PIds(p2pIds) on Ethereum Mainnet. + * Use viem's createPublicClient + readContract, or any Ethereum JSON-RPC client. + * See report.ts in cre-sdk-typescript for the exact ABI encoding/decoding. + */ +async function fetchSigners(donId: number): Promise<{ f: number; signers: Set }> { + // TODO: implement using your Ethereum Mainnet RPC + // Registry: 0x76c9cf548b4179F8901cda1f8623568b58215E62 + throw new Error("implement: call getDON(donId) then getNodesByP2PIds(p2pIds)") +} + +/** Verify that ≥ f+1 signatures are from authorized DON signers. */ +async function verifyReport(rawReport: Uint8Array, signatures: Uint8Array[], reportContext: Uint8Array): Promise { + const { donId } = parseHeader(rawReport) + const { f, signers } = await fetchSigners(donId) + const hash = reportHash(rawReport, reportContext) + const required = f + 1 + let valid = 0 + + for (const sig of signatures) { + if (sig.length !== 65) continue + const normalized = new Uint8Array(sig) + if (normalized[64] >= 27) normalized[64] -= 27 // normalize recovery byte + try { + const recovered = await recoverAddress({ hash, signature: toHex(normalized) }) + if (signers.has(recovered.toLowerCase().slice(2))) valid++ + } catch { + /* malformed signature — skip */ + } + if (valid >= required) return + } + throw new Error(`insufficient valid signatures: ${valid}/${required}`) +} + +// Usage — given hex fields from your API POST body: +const rawReport = hexToBytes(`0x${payload.report}`) +const reportContext = hexToBytes(`0x${payload.context}`) +const signatures = payload.signatures.map((s: string) => hexToBytes(`0x${s}`)) + +await verifyReport(rawReport, signatures, reportContext) +// If it doesn't throw, the report is authentic. +// Read the payload: parseHeader(rawReport).body +``` + +{/* prettier-ignore */} + + +## CRE receiver workflow + +Use this path if you want the receiver itself to be a CRE workflow (with an HTTP trigger). The CRE SDK's [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) handles registry reads and caching automatically. + +{/* prettier-ignore */} + + +{/* prettier-ignore */} + + +### Testing locally with simulation After you run the [submit guide complete example](/cre/guides/workflow/using-http-client/submitting-reports-http-ts#complete-working-example) and copy JSON from webhook.site, use this section to exercise a receiver workflow in simulation. diff --git a/src/content/cre/llms-full-go.txt b/src/content/cre/llms-full-go.txt index fde695841ea..32c599e5c53 100644 --- a/src/content/cre/llms-full-go.txt +++ b/src/content/cre/llms-full-go.txt @@ -15981,47 +15981,34 @@ This guide is for the **receiver** side: you already received a CRE report packa When a workflow delivers results via HTTP (or another offchain channel), nothing onchain automatically validates the report. **You must verify signatures before trusting the data.** -The CRE SDK provides [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) to do this inside a workflow. Verification runs offchain in your callback: signatures are checked with local cryptography, while authorized signer addresses are loaded via **read-only calls to the onchain Capability Registry** (default: Ethereum Mainnet). Results are cached per DON. +There are two ways to verify, depending on who receives the POST: +| My receiver is... | Use | +| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **My own API or server** (Go HTTP server, etc.) | [Verifying outside CRE](#verifying-outside-cre) — standard crypto libraries, no CRE SDK needed | +| **A CRE workflow** with an HTTP trigger | [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) — see [Testing locally with simulation](#testing-locally-with-simulation) and the [deploy example](#complete-example-http-receiver-workflow-deploy) | - - - - +Both paths run the same cryptographic checks against the same onchain Capability Registry. The difference is whether you use the CRE SDK's helper (which requires `runtime`) or implement it yourself with standard libraries. ## What is a CRE report? A **[CRE report](/cre/key-terms#report-cre-report)** is a DON-signed package another workflow (or system) created with [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values). You receive its bytes over HTTP (or another channel) as `rawReport`, `reportContext`, and `signatures`. Before you use the encoded payload, you must confirm the signatures match authorized DON signers on the Capability Registry. -This guide is the **receiver** path: decode the POST body, call [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport), then read trusted metadata and `Body()`. See [Key Terms: Report](/cre/key-terms#report-cre-report) for how reports are created and delivered. - -## Where this guide fits - -| Question | Answer | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| What is the report? | Same [CRE report](/cre/key-terms#report-cre-report) the **sender** created with [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values). See [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-go#what-is-a-cre-report). | -| Where does it come from? | Another workflow (or system) already ran sender steps: logic → [`runtime.GenerateReport()`](/cre/guides/workflow/using-evm-client/onchain-write/generating-reports-single-values) → HTTP POST. You receive `rawReport`, `context`, and `signatures` in the request body. | -| What does this guide cover? | Step 3 below: [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) before you use `Body()` or take side effects. | -| Same workflow as the sender? | Often **no:** common pattern is Workflow A (publish) and Workflow B (ingest with HTTP trigger). | +See [Key Terms: Report](/cre/key-terms#report-cre-report) for how reports are created and delivered. Pair this guide with [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-go) on the sender side. -**Receiver flow:** +**Receiver flow (both paths):** -1. HTTP trigger (or your API) receives the POST payload. +1. Your endpoint (API or CRE HTTP trigger) receives the POST payload. 2. Decode hex fields into bytes. -3. [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport): verify signatures and read metadata. -4. Use trusted `Body()` in your logic. - -Pair this guide with [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-go) on the sender side. This guide covers local simulation first, then the deploy example with `AuthorizedKeys`. +3. Verify signatures against the Capability Registry. +4. Use the trusted payload body in your logic. ## What you'll learn - When to verify reports offchain vs relying on onchain forwarders -- How [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) validates signatures and reads metadata -- How to build a receiver workflow that accepts reports over HTTP +- How [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) validates signatures and reads metadata (inside a CRE workflow) +- How to build a CRE receiver workflow that accepts reports over HTTP +- How to verify reports in your own API server without a CRE receiver workflow - How to restrict verification to specific CRE environments or zones ## Prerequisites @@ -16037,7 +16024,7 @@ Pair this guide with [Submitting Reports via HTTP](/cre/guides/workflow/using-ht | **Where it runs** | Inside your CRE workflow callback | In a smart contract transaction | | **Signature check** | Local `ecrecover` on report hash | Contract logic onchain | | **Signer allowlist** | Read from Capability Registry (`getDON`, `getNodesByP2PIds`) | Forwarder + registry | -| **Typical use** | HTTP APIs, webhooks, ingest workflows | Consumer contracts via `onReport` | +| **Typical use** | CRE receiver workflows with an HTTP trigger | Consumer contracts via `onReport` | Offchain verification still uses **onchain data as a trust anchor**: the first time a DON is seen, the SDK reads the production Capability Registry on Ethereum Mainnet to learn `f` and authorized signer addresses. @@ -16055,7 +16042,130 @@ Default (`cre.ProductionEnvironment()`): If verification fails, [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) returns an error (for example, `ErrUnknownSigner`, `ErrWrongSignatureCount`, or registry read failure). -## Testing locally with simulation +## Verifying outside CRE + +[`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) requires the CRE `runtime` object, so it can only run inside a CRE workflow callback. If your API server receives reports directly, you can implement the same verification steps with standard libraries — no CRE SDK required. + +**What you need:** + +- An Ethereum Mainnet RPC endpoint (for Capability Registry reads) +- `github.com/ethereum/go-ethereum/crypto` for `Keccak256` and signature recovery + +**Verification steps:** + +1. Decode the hex JSON fields into bytes. +2. Parse the **109-byte metadata header** from `rawReport` to extract `donId`, `workflowId`, `workflowOwner`, and `body`. +3. Compute the message hash: `keccak256(keccak256(rawReport) || reportContext)`. +4. Fetch the DON's fault tolerance `f` and authorized signer addresses from the **Capability Registry** on Ethereum Mainnet (`0x76c9cf548b4179F8901cda1f8623568b58215E62`): call `getDON(donId)` then `getNodesByP2PIds(p2pIds)`. +5. For each signature (65 bytes), recover the signing address. Require at least **f+1** addresses from the authorized signer set. + +The following sketch shows the crypto steps. The Capability Registry call (step 4) follows the same `getDON` → `getNodesByP2PIds` ABI logic used in the CRE SDK; see [`report_verification.go` in cre-sdk-go](https://github.com/smartcontractkit/cre-sdk-go) for the exact call and decoding logic. + +```go +package main + +import ( + "encoding/binary" + "encoding/hex" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" +) + +const ( + reportHeaderLength = 109 + capabilityRegistry = "0x76c9cf548b4179F8901cda1f8623568b58215E62" +) + +// parseHeader extracts key fields from the 109-byte rawReport metadata header. +func parseHeader(rawReport []byte) (donId uint32, workflowId string, body []byte, err error) { + if len(rawReport) < reportHeaderLength { + return 0, "", nil, fmt.Errorf("rawReport too short: need %d bytes, got %d", reportHeaderLength, len(rawReport)) + } + donId = binary.BigEndian.Uint32(rawReport[37:41]) + workflowId = hex.EncodeToString(rawReport[45:77]) + body = rawReport[reportHeaderLength:] + return +} + +// reportHash computes keccak256(keccak256(rawReport) || reportContext). +func reportHash(rawReport, reportContext []byte) []byte { + inner := crypto.Keccak256(rawReport) + return crypto.Keccak256(append(inner, reportContext...)) +} + +// fetchSigners calls the Capability Registry on Ethereum Mainnet to get +// the fault tolerance f and authorized signer addresses for the given DON. +// Use go-ethereum's ethclient and ABI encoding. +// See report_verification.go in cre-sdk-go for the exact call and decoding logic. +func fetchSigners(client *ethclient.Client, donId uint32) (f int, signers map[common.Address]bool, err error) { + // TODO: implement using your Ethereum Mainnet RPC + // 1. ABI-encode getDON(donId), call capabilityRegistry, decode f and nodeP2PIds + // 2. ABI-encode getNodesByP2PIds(nodeP2PIds), call capabilityRegistry, decode signer addresses + return 0, nil, fmt.Errorf("implement: call getDON then getNodesByP2PIds on the Capability Registry") +} + +// verifyReport checks that ≥ f+1 signatures are from authorized DON signers. +func verifyReport(rawReport []byte, signatures [][]byte, reportContext []byte, client *ethclient.Client) error { + donId, _, _, err := parseHeader(rawReport) + if err != nil { + return err + } + f, signers, err := fetchSigners(client, donId) + if err != nil { + return err + } + hash := reportHash(rawReport, reportContext) + required := f + 1 + valid := 0 + + for _, sig := range signatures { + if len(sig) != 65 { + continue + } + norm := make([]byte, 65) + copy(norm, sig) + if norm[64] >= 27 { + norm[64] -= 27 // normalize recovery byte + } + pubKey, err := crypto.SigToPub(hash, norm) + if err != nil { + continue + } + addr := crypto.PubkeyToAddress(*pubKey) + if signers[addr] { + valid++ + } + if valid >= required { + return nil + } + } + return fmt.Errorf("insufficient valid signatures: %d/%d", valid, required) +} +``` + + + + +## CRE receiver workflow + +Use this path if you want the receiver itself to be a CRE workflow (with an HTTP trigger). The CRE SDK's [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) handles registry reads and caching automatically. + + + + + + + +### Testing locally with simulation After you run the [submit guide complete example](/cre/guides/workflow/using-http-client/submitting-reports-http-go#complete-working-example) and copy JSON from webhook.site, use this section to exercise a receiver workflow in simulation. diff --git a/src/content/cre/llms-full-ts.txt b/src/content/cre/llms-full-ts.txt index 3e58f8ca273..faa30f6ad84 100644 --- a/src/content/cre/llms-full-ts.txt +++ b/src/content/cre/llms-full-ts.txt @@ -15716,47 +15716,34 @@ This guide is for the **receiver** side: you already received a CRE report packa When a workflow delivers results via HTTP (or another offchain channel), nothing onchain automatically validates the report. **You must verify signatures before trusting the data.** -The CRE SDK provides [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) to do this inside a workflow. Verification runs **offchain** in your callback: signatures are checked with local cryptography, while authorized signer addresses are loaded via **read-only calls to the onchain Capability Registry** (default: Ethereum Mainnet). Results are cached per DON. +There are two ways to verify, depending on who receives the POST: +| My receiver is... | Use | +| ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **My own API or server** (Express, FastAPI, Go server, etc.) | [Verifying outside CRE](#verifying-outside-cre) — standard crypto libraries, no CRE SDK needed | +| **A CRE workflow** with an HTTP trigger | [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) — see [Testing locally with simulation](#testing-locally-with-simulation) and the [deploy example](#complete-example-http-receiver-workflow-deploy) | - - - - +Both paths run the same cryptographic checks against the same onchain Capability Registry. The difference is whether you use the CRE SDK's helper (which requires `runtime`) or implement it yourself with standard libraries. ## What is a CRE report? A **[CRE report](/cre/key-terms#report-cre-report)** is a DON-signed package another workflow (or system) created with [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime). You receive its bytes over HTTP (or another channel) as `rawReport`, `reportContext`, and `signatures`. Before you use the encoded payload, you must confirm the signatures match authorized DON signers on the Capability Registry. -This guide is the **receiver** path: decode the POST body, call [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification), then read trusted metadata and `body()`. See [Key Terms: Report](/cre/key-terms#report-cre-report) for how reports are created and delivered. - -## Where this guide fits - -| Question | Answer | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| What is the report? | Same [CRE report](/cre/key-terms#report-cre-report) the **sender** created with [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime). See [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts#what-is-a-cre-report). | -| Where does it come from? | Another workflow (or system) already ran sender steps: logic → [`runtime.report()`](/cre/reference/sdk/core-ts#runtime-and-noderuntime) → HTTP POST. You receive `rawReport`, `context`, and `signatures` in the request body. | -| What does this guide cover? | Step 3 below: [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) before you use `body()` or take side effects. | -| Same workflow as the sender? | Often **no:** common pattern is Workflow A (publish) and Workflow B (ingest with HTTP trigger). | +See [Key Terms: Report](/cre/key-terms#report-cre-report) for how reports are created and delivered. Pair this guide with [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts) on the sender side. -**Receiver flow:** +**Receiver flow (both paths):** -1. HTTP trigger (or your API) receives the POST payload. +1. Your endpoint (API or CRE HTTP trigger) receives the POST payload. 2. Decode hex fields into bytes. -3. [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification): verify signatures and read metadata. -4. Use trusted `body()` in your logic. - -Pair this guide with [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-ts) on the sender side. This guide covers local simulation first, then the deploy example with `authorizedKeys`. +3. Verify signatures against the Capability Registry. +4. Use the trusted payload body in your logic. ## What you'll learn - When to verify reports offchain vs relying on onchain forwarders -- How [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) validates signatures and reads metadata -- How to build a receiver workflow that accepts reports over HTTP +- How [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) validates signatures and reads metadata (inside a CRE workflow) +- How to build a CRE receiver workflow that accepts reports over HTTP +- How to verify reports in your own API server without a CRE receiver workflow - How to restrict verification to specific CRE environments or zones ## Prerequisites @@ -15772,7 +15759,7 @@ Pair this guide with [Submitting Reports via HTTP](/cre/guides/workflow/using-ht | **Where it runs** | Inside your CRE workflow callback | In a smart contract transaction | | **Signature check** | Local `ecrecover` on report hash | Contract logic onchain | | **Signer allowlist** | Read from Capability Registry (`getDON`, `getNodesByP2PIds`) | Forwarder + registry | -| **Typical use** | HTTP APIs, webhooks, ingest workflows | Consumer contracts via `onReport` | +| **Typical use** | CRE receiver workflows with an HTTP trigger | Consumer contracts via `onReport` | Offchain verification still uses **onchain data as a trust anchor**: the first time a DON is seen, the SDK reads the production Capability Registry on Ethereum Mainnet to learn `f` and authorized signer addresses. @@ -15790,7 +15777,114 @@ Default (`productionEnvironment()`): If verification fails, [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) throws (for example, unknown signer, insufficient signatures, or registry read failure). -## Testing locally with simulation +## Verifying outside CRE + +[`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) requires the CRE `runtime` object, so it can only run inside a CRE workflow callback. If your API server receives reports directly, you can implement the same verification steps with standard libraries — no CRE SDK required. + +**What you need:** + +- An Ethereum Mainnet RPC endpoint (for Capability Registry reads) +- `viem` for the cryptographic primitives (`keccak256`, `recoverAddress`) + +**Verification steps:** + +1. Decode the hex JSON fields into bytes. +2. Parse the **109-byte metadata header** from `rawReport` to extract `donId`, `workflowId`, `workflowOwner`, and `body`. +3. Compute the message hash: `keccak256(keccak256(rawReport) || reportContext)`. +4. Fetch the DON's fault tolerance `f` and authorized signer addresses from the **Capability Registry** on Ethereum Mainnet (`0x76c9cf548b4179F8901cda1f8623568b58215E62`): call `getDON(donId)` then `getNodesByP2PIds(p2pIds)`. +5. For each signature (65 bytes), `ecrecover` the signing address. Require at least **f+1** addresses from the authorized signer set. + +The following sketch shows the crypto steps. The Capability Registry call (step 4) follows the same `getDON` → `getNodesByP2PIds` ABI logic used in the CRE SDK; see [`report.ts` in cre-sdk-typescript](https://github.com/smartcontractkit/cre-sdk-typescript) for the exact ABI encoding and decoding. + +```typescript +import { keccak256, concatHex, toHex, recoverAddress } from "viem" + +const REPORT_HEADER_LENGTH = 109 + +/** Parse key fields from the 109-byte rawReport metadata header. */ +function parseHeader(rawReport: Uint8Array) { + if (rawReport.length < REPORT_HEADER_LENGTH) { + throw new Error(`rawReport too short: need ${REPORT_HEADER_LENGTH} bytes, got ${rawReport.length}`) + } + const view = new DataView(rawReport.buffer, rawReport.byteOffset) + return { + donId: view.getUint32(37, false), // bytes 37–40 + workflowId: toHex(rawReport.slice(45, 77)), // bytes 45–76 + workflowOwner: toHex(rawReport.slice(87, 107)), // bytes 87–106 + body: rawReport.slice(REPORT_HEADER_LENGTH), // bytes 109+ + } +} + +/** keccak256(keccak256(rawReport) || reportContext) */ +function reportHash(rawReport: Uint8Array, reportContext: Uint8Array): `0x${string}` { + return keccak256(concatHex([keccak256(toHex(rawReport)), toHex(reportContext)])) +} + +/** + * Fetch fault tolerance f and authorized signer addresses from the Capability Registry. + * Calls getDON(donId) → getNodesByP2PIds(p2pIds) on Ethereum Mainnet. + * Use viem's createPublicClient + readContract, or any Ethereum JSON-RPC client. + * See report.ts in cre-sdk-typescript for the exact ABI encoding/decoding. + */ +async function fetchSigners(donId: number): Promise<{ f: number; signers: Set }> { + // TODO: implement using your Ethereum Mainnet RPC + // Registry: 0x76c9cf548b4179F8901cda1f8623568b58215E62 + throw new Error("implement: call getDON(donId) then getNodesByP2PIds(p2pIds)") +} + +/** Verify that ≥ f+1 signatures are from authorized DON signers. */ +async function verifyReport(rawReport: Uint8Array, signatures: Uint8Array[], reportContext: Uint8Array): Promise { + const { donId } = parseHeader(rawReport) + const { f, signers } = await fetchSigners(donId) + const hash = reportHash(rawReport, reportContext) + const required = f + 1 + let valid = 0 + + for (const sig of signatures) { + if (sig.length !== 65) continue + const normalized = new Uint8Array(sig) + if (normalized[64] >= 27) normalized[64] -= 27 // normalize recovery byte + try { + const recovered = await recoverAddress({ hash, signature: toHex(normalized) }) + if (signers.has(recovered.toLowerCase().slice(2))) valid++ + } catch { + /* malformed signature — skip */ + } + if (valid >= required) return + } + throw new Error(`insufficient valid signatures: ${valid}/${required}`) +} + +// Usage — given hex fields from your API POST body: +const rawReport = hexToBytes(`0x${payload.report}`) +const reportContext = hexToBytes(`0x${payload.context}`) +const signatures = payload.signatures.map((s: string) => hexToBytes(`0x${s}`)) + +await verifyReport(rawReport, signatures, reportContext) +// If it doesn't throw, the report is authentic. +// Read the payload: parseHeader(rawReport).body +``` + + + + +## CRE receiver workflow + +Use this path if you want the receiver itself to be a CRE workflow (with an HTTP trigger). The CRE SDK's [`Report.parse()`](/cre/reference/sdk/core-ts#report-verification) handles registry reads and caching automatically. + + + + + + + +### Testing locally with simulation After you run the [submit guide complete example](/cre/guides/workflow/using-http-client/submitting-reports-http-ts#complete-working-example) and copy JSON from webhook.site, use this section to exercise a receiver workflow in simulation. From 0e5ef8292246d770c2a6017da10a4d6785054a16 Mon Sep 17 00:00:00 2001 From: devin distefano Date: Wed, 10 Jun 2026 22:43:27 -0500 Subject: [PATCH 8/9] major update --- .../verifying-reports-offchain-go.mdx | 774 +++++++++--------- .../verifying-reports-offchain-ts.mdx | 665 +++++++-------- src/content/cre/llms-full-go.txt | 664 ++++++++------- src/content/cre/llms-full-ts.txt | 577 ++++++------- 4 files changed, 1359 insertions(+), 1321 deletions(-) diff --git a/src/content/cre/guides/workflow/using-http-client/verifying-reports-offchain-go.mdx b/src/content/cre/guides/workflow/using-http-client/verifying-reports-offchain-go.mdx index 2710d920dc5..faba443197e 100644 --- a/src/content/cre/guides/workflow/using-http-client/verifying-reports-offchain-go.mdx +++ b/src/content/cre/guides/workflow/using-http-client/verifying-reports-offchain-go.mdx @@ -12,16 +12,16 @@ metadata: import { Aside } from "@components" -This guide is for the **receiver** side: you already received a CRE report package (usually via HTTP) and need to **prove it is authentic** before using the payload. +This guide is for the **receiver** side: you already received a CRE report package — typically via HTTP from a [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-go) sender workflow — and need to **prove it is authentic** before using the payload. When a workflow delivers results via HTTP (or another offchain channel), nothing onchain automatically validates the report. **You must verify signatures before trusting the data.** There are two ways to verify, depending on who receives the POST: -| My receiver is... | Use | -| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **My own API or server** (Go HTTP server, etc.) | [Verifying outside CRE](#verifying-outside-cre) — standard crypto libraries, no CRE SDK needed | -| **A CRE workflow** with an HTTP trigger | [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) — see [Testing locally with simulation](#testing-locally-with-simulation) and the [deploy example](#complete-example-http-receiver-workflow-deploy) | +| My receiver is... | Use | +| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | +| **My own API or server** (Go HTTP server, etc.) | [Verifying outside CRE](#verifying-outside-cre) — standard crypto libraries, no CRE SDK needed | +| **A CRE workflow** with an HTTP trigger | [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) — see [CRE receiver workflow](#cre-receiver-workflow) | Both paths run the same cryptographic checks against the same onchain Capability Registry. The difference is whether you use the CRE SDK's helper (which requires `runtime`) or implement it yourself with standard libraries. @@ -48,8 +48,8 @@ See [Key Terms: Report](/cre/key-terms#report-cre-report) for how reports are cr ## Prerequisites -- **SDK**: `cre-sdk-go` v1.8.0 or later (report verification support) -- Familiarity with [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-go) (report structure and JSON payload patterns) +- **A report payload to verify** — three hex fields: `report`, `context`, `signatures`. If you don't have one yet, follow [Submitting Reports via HTTP](/cre/guides/workflow/using-http-client/submitting-reports-http-go) to create a sender workflow and capture its output first. +- **SDK**: `cre-sdk-go` v1.8.0 or later (for the CRE receiver workflow path) - For HTTP-triggered receivers: [HTTP Trigger configuration](/cre/guides/workflow/using-triggers/http-trigger/configuration-go) ## Onchain vs offchain verification @@ -70,6 +70,8 @@ Default (`cre.ProductionEnvironment()`): ## How verification works +At its core, verification answers one question: did enough authorized nodes from the right DON sign this exact report? To answer it, the receiver decodes the report's metadata header to find which DON produced it, asks the onchain Capability Registry for that DON's authorized signers and quorum threshold (`f`), then checks that at least `f+1` of the provided signatures come from those addresses. Both paths in this guide — the outside-CRE program and the CRE receiver workflow — run this same sequence: + 1. **Parse the report header** from `rawReport` (109-byte metadata + body). 2. **Fetch DON info** from the registry (if not cached): fault tolerance `f` and signer addresses. 3. **Verify signatures**: compute `keccak256(keccak256(rawReport) || reportContext)`, recover signers, require **f+1** valid signatures from authorized nodes. @@ -79,348 +81,393 @@ If verification fails, [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparse ## Verifying outside CRE -[`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) requires the CRE `runtime` object, so it can only run inside a CRE workflow callback. If your API server receives reports directly, you can implement the same verification steps with standard libraries — no CRE SDK required. - -**What you need:** - -- An Ethereum Mainnet RPC endpoint (for Capability Registry reads) -- `github.com/ethereum/go-ethereum/crypto` for `Keccak256` and signature recovery - -**Verification steps:** - -1. Decode the hex JSON fields into bytes. -2. Parse the **109-byte metadata header** from `rawReport` to extract `donId`, `workflowId`, `workflowOwner`, and `body`. -3. Compute the message hash: `keccak256(keccak256(rawReport) || reportContext)`. -4. Fetch the DON's fault tolerance `f` and authorized signer addresses from the **Capability Registry** on Ethereum Mainnet (`0x76c9cf548b4179F8901cda1f8623568b58215E62`): call `getDON(donId)` then `getNodesByP2PIds(p2pIds)`. -5. For each signature (65 bytes), recover the signing address. Require at least **f+1** addresses from the authorized signer set. - -The following sketch shows the crypto steps. The Capability Registry call (step 4) follows the same `getDON` → `getNodesByP2PIds` ABI logic used in the CRE SDK; see [`report_verification.go` in cre-sdk-go](https://github.com/smartcontractkit/cre-sdk-go) for the exact call and decoding logic. - -```go -package main - -import ( - "encoding/binary" - "encoding/hex" - "fmt" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethclient" -) - -const ( - reportHeaderLength = 109 - capabilityRegistry = "0x76c9cf548b4179F8901cda1f8623568b58215E62" -) - -// parseHeader extracts key fields from the 109-byte rawReport metadata header. -func parseHeader(rawReport []byte) (donId uint32, workflowId string, body []byte, err error) { - if len(rawReport) < reportHeaderLength { - return 0, "", nil, fmt.Errorf("rawReport too short: need %d bytes, got %d", reportHeaderLength, len(rawReport)) - } - donId = binary.BigEndian.Uint32(rawReport[37:41]) - workflowId = hex.EncodeToString(rawReport[45:77]) - body = rawReport[reportHeaderLength:] - return -} - -// reportHash computes keccak256(keccak256(rawReport) || reportContext). -func reportHash(rawReport, reportContext []byte) []byte { - inner := crypto.Keccak256(rawReport) - return crypto.Keccak256(append(inner, reportContext...)) -} - -// fetchSigners calls the Capability Registry on Ethereum Mainnet to get -// the fault tolerance f and authorized signer addresses for the given DON. -// Use go-ethereum's ethclient and ABI encoding. -// See report_verification.go in cre-sdk-go for the exact call and decoding logic. -func fetchSigners(client *ethclient.Client, donId uint32) (f int, signers map[common.Address]bool, err error) { - // TODO: implement using your Ethereum Mainnet RPC - // 1. ABI-encode getDON(donId), call capabilityRegistry, decode f and nodeP2PIds - // 2. ABI-encode getNodesByP2PIds(nodeP2PIds), call capabilityRegistry, decode signer addresses - return 0, nil, fmt.Errorf("implement: call getDON then getNodesByP2PIds on the Capability Registry") -} - -// verifyReport checks that ≥ f+1 signatures are from authorized DON signers. -func verifyReport(rawReport []byte, signatures [][]byte, reportContext []byte, client *ethclient.Client) error { - donId, _, _, err := parseHeader(rawReport) - if err != nil { - return err - } - f, signers, err := fetchSigners(client, donId) - if err != nil { - return err - } - hash := reportHash(rawReport, reportContext) - required := f + 1 - valid := 0 - - for _, sig := range signatures { - if len(sig) != 65 { - continue - } - norm := make([]byte, 65) - copy(norm, sig) - if norm[64] >= 27 { - norm[64] -= 27 // normalize recovery byte - } - pubKey, err := crypto.SigToPub(hash, norm) - if err != nil { - continue - } - addr := crypto.PubkeyToAddress(*pubKey) - if signers[addr] { - valid++ - } - if valid >= required { - return nil - } - } - return fmt.Errorf("insufficient valid signatures: %d/%d", valid, required) -} -``` +[`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) requires the CRE `runtime` object and can only run inside a CRE workflow callback. If your API server receives reports directly, you can verify without the CRE SDK using standard libraries. + +If you followed the [submit guide](/cre/guides/workflow/using-http-client/submitting-reports-http-go), you already have a JSON payload with `report`, `context`, and `signatures` as hex strings — this section shows how to verify it. + +1. Create a new folder for your verification program and initialize a module: + + ```bash + mkdir verify-report && cd verify-report + go mod init verify-report + go get github.com/ethereum/go-ethereum + ``` + +1. Save the following as `main.go`. + + Parses the 109-byte report header to identify the DON, reads the authorized signer list and fault tolerance `f` from the Capability Registry on Ethereum Mainnet, then recovers each signature and requires at least `f+1` to match. The ABI layout mirrors [`report_verification.go` in cre-sdk-go](https://github.com/smartcontractkit/cre-sdk-go). + + ```go + // verify-report/main.go + package main + + import ( + "context" + "encoding/binary" + "encoding/hex" + "fmt" + "math/big" + "sync" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + ) + + const ( + reportHeaderLength = 109 + capabilityRegistry = "0x76c9cf548b4179F8901cda1f8623568b58215E62" + ) + + // parseHeader extracts key fields from the 109-byte rawReport metadata header. + func parseHeader(rawReport []byte) (donId uint32, workflowId string, body []byte, err error) { + if len(rawReport) < reportHeaderLength { + return 0, "", nil, fmt.Errorf("rawReport too short: need %d bytes, got %d", reportHeaderLength, len(rawReport)) + } + donId = binary.BigEndian.Uint32(rawReport[37:41]) + workflowId = hex.EncodeToString(rawReport[45:77]) + body = rawReport[reportHeaderLength:] + return + } + + // reportHash computes keccak256(keccak256(rawReport) || reportContext). + func reportHash(rawReport, reportContext []byte) []byte { + inner := crypto.Keccak256(rawReport) + return crypto.Keccak256(append(inner, reportContext...)) + } + + // padUint256 encodes a uint64 as a big-endian 32-byte ABI word. + func padUint256(v uint64) []byte { + b := make([]byte, 32) + binary.BigEndian.PutUint64(b[24:], v) + return b + } + + type donInfo struct { + f int + signers map[common.Address]bool + } + + // signerCache caches registry lookups by DON ID. Registry data only changes + // during DON reconfiguration; clear and retry if you see unexpected signer errors after a DON upgrade. + var signerCache sync.Map + + // fetchSigners calls the Capability Registry on Ethereum Mainnet to get + // the fault tolerance f and authorized signer addresses for the given DON. + // Makes two eth_call reads: getDON(donId) then getNodesByP2PIds(nodeP2PIds). + // Result is cached by DON ID. + func fetchSigners(client *ethclient.Client, donId uint32) (f int, signers map[common.Address]bool, err error) { + if cached, ok := signerCache.Load(donId); ok { + info := cached.(donInfo) + return info.f, info.signers, nil + } + + registry := common.HexToAddress(capabilityRegistry) + ctx := context.Background() + + // Step 1: getDON(uint32 donId) — selector keccak256("getDON(uint32)")[0:4] = 0x23537405 + var donIdPadded [32]byte + binary.BigEndian.PutUint32(donIdPadded[28:], donId) + getDONCalldata := append([]byte{0x23, 0x53, 0x74, 0x05}, donIdPadded[:]...) + + getDONBytes, err := client.CallContract(ctx, ethereum.CallMsg{To: ®istry, Data: getDONCalldata}, nil) + if err != nil { + return 0, nil, fmt.Errorf("getDON call failed: %w", err) + } + if len(getDONBytes) < 224 { + return 0, nil, fmt.Errorf("getDON response too short: %d bytes", len(getDONBytes)) + } + + // Response layout (see report_verification.go in cre-sdk-go for full ABI documentation): + // slot 3 (bytes 96-127): f (uint8, zero-padded to 32) + // slot 6 (bytes 192-223): ptr[nodeP2PIds] relative to tupleStart = 32 + f = int(new(big.Int).SetBytes(getDONBytes[96:128]).Int64()) + nodeP2PIdsPtr := int(new(big.Int).SetBytes(getDONBytes[192:224]).Int64()) + nodeCountOff := 32 + nodeP2PIdsPtr + nodeCount := int(new(big.Int).SetBytes(getDONBytes[nodeCountOff : nodeCountOff+32]).Int64()) + + nodeP2PIds := make([][]byte, nodeCount) + for i := 0; i < nodeCount; i++ { + start := nodeCountOff + 32 + i*32 + id := make([]byte, 32) + copy(id, getDONBytes[start:start+32]) + nodeP2PIds[i] = id + } + + if nodeCount == 0 { + info := donInfo{f: f, signers: nil} + signerCache.Store(donId, info) + return f, nil, nil + } + + // Step 2: getNodesByP2PIds(bytes32[]) — selector 0x05a51966 + // ABI-encode bytes32[]: [ptr=32][count][id0]...[idN] + getNodesCalldata := append([]byte{0x05, 0xa5, 0x19, 0x66}, padUint256(32)...) + getNodesCalldata = append(getNodesCalldata, padUint256(uint64(nodeCount))...) + for _, id := range nodeP2PIds { + getNodesCalldata = append(getNodesCalldata, id...) + } + + getNodesBytes, err := client.CallContract(ctx, ethereum.CallMsg{To: ®istry, Data: getNodesCalldata}, nil) + if err != nil { + return 0, nil, fmt.Errorf("getNodesByP2PIds call failed: %w", err) + } + if len(getNodesBytes) < 64 { + return 0, nil, fmt.Errorf("getNodesByP2PIds response too short: %d bytes", len(getNodesBytes)) + } + + // Response layout: [outerPtr][count][elem0-ptr][elem1-ptr]...[tuple0][tuple1]... + // Each NodeInfo tuple (9 slots × 32 bytes): slot 3 = signer bytes32, first 20 bytes = address + outerPtr := int(new(big.Int).SetBytes(getNodesBytes[0:32]).Int64()) + returnedCount := int(new(big.Int).SetBytes(getNodesBytes[outerPtr : outerPtr+32]).Int64()) + const nodeTupleHead = 288 // 9 slots × 32 bytes + + signers = make(map[common.Address]bool, returnedCount) + for i := 0; i < returnedCount; i++ { + elemPtrOff := outerPtr + 32 + i*32 + elemPtr := int(new(big.Int).SetBytes(getNodesBytes[elemPtrOff : elemPtrOff+32]).Int64()) + tupleBase := outerPtr + 32 + elemPtr + if tupleBase+nodeTupleHead > len(getNodesBytes) { + break + } + signerSlot := tupleBase + 3*32 + addr := common.BytesToAddress(getNodesBytes[signerSlot : signerSlot+20]) + signers[addr] = true + } + + signerCache.Store(donId, donInfo{f: f, signers: signers}) + return f, signers, nil + } + + // verifyReport checks that ≥ f+1 signatures are from authorized DON signers. + func verifyReport(rawReport []byte, signatures [][]byte, reportContext []byte, client *ethclient.Client) error { + donId, _, _, err := parseHeader(rawReport) + if err != nil { + return err + } + f, signers, err := fetchSigners(client, donId) + if err != nil { + return err + } + hash := reportHash(rawReport, reportContext) + required := f + 1 + valid := 0 + + for _, sig := range signatures { + if len(sig) != 65 { + continue + } + norm := make([]byte, 65) + copy(norm, sig) + if norm[64] >= 27 { + norm[64] -= 27 // normalize recovery byte + } + pubKey, err := crypto.SigToPub(hash, norm) + if err != nil { + continue + } + addr := crypto.PubkeyToAddress(*pubKey) + if signers[addr] { + valid++ + } + if valid >= required { + return nil + } + } + return fmt.Errorf("insufficient valid signatures: %d/%d", valid, required) + } + + // Usage — create the client once for your server, reuse it across requests: + // + // client, err := ethclient.Dial(os.Getenv("ETH_MAINNET_RPC_URL")) + // + // For each incoming report POST: + // if err := verifyReport(rawReport, signatures, reportContext, client); err != nil { + // // reject the request + // } + // // verified: use parseHeader(rawReport) to read the payload body + ``` + +1. Set your RPC URL and run: + + ```bash + export ETH_MAINNET_RPC_URL=https://your-mainnet-rpc-endpoint + go run main.go + ``` + +1. Check the output. + + With a **sim-signed report** (from the submit guide sender simulation): + + ``` + insufficient valid signatures: 0/4 + ``` + + This is correct. Simulation uses local test keys that are not registered in the mainnet Capability Registry. The registry call succeeded and returned the real signer list; none of the sim signatures matched it. The verification logic is working as intended. + + With a **production-signed report** from a deployed sender, `verifyReport` returns `nil`. Read the payload: + + ```go + donId, workflowId, body, err := parseHeader(rawReport) + // body is the ABI-encoded payload the sender embedded + ``` {/* prettier-ignore */} ## CRE receiver workflow -Use this path if you want the receiver itself to be a CRE workflow (with an HTTP trigger). The CRE SDK's [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) handles registry reads and caching automatically. +Use this path if you want the receiver itself to be a CRE workflow with an HTTP trigger. [`cre.ParseReport()`](/cre/reference/sdk/core-go#creparsereport) handles Capability Registry reads and caching automatically. {/* prettier-ignore */} -