# Resolution Attestation Profile v1 (RAP-1)

**A machine-checkable way for an off-chain resolver to show a venue that it followed its own declared resolution procedure.**

| | |
|---|---|
| Profile ID | `RAP-1` |
| Taxonomy version | `1.1.0` |
| Status | Published specification. **No venue has adopted it. No regulator has reviewed it.** |
| Reference implementation | `security.process-attestation` — BlindOracle SKU, $0.25/call |
| Endpoint | `POST https://api.craigmbrown.com/v1/services/security.process-attestation` |
| Identity binding | ERC-8004 passport (optional — see §8) |
| Published | 2026-09-05 (v1.1.0, same day) |
| Author | Craig M. Brown / BlindOracle |
| License | This document may be freely implemented. |

> **What this proves, and what it does not.** RAP-1 proves that a submitted evidence
> log is internally consistent with a declared procedure. It does **not** prove the
> log is a true record of what happened — a fabricated but internally consistent log
> scores identically to a genuine one. Under the symmetric `hmac-sha256` scheme it
> also does not prove *who* produced the evidence; use `ed25519` (§7.3) when
> attribution matters. Every deliverable carries these limits as machine-readable
> fields (`evidence_basis`, `limitation`, `signature_binding`), not merely as prose.

---

## 1. The problem

An event contract settles against an outcome. Somebody has to determine that outcome.
At a CFTC-regulated venue that determination is a **human process against a
pre-specified authoritative source** — Kalshi, for example, resolves against official
government statistics, league results and regulatory announcements, with an Outcome
Review Committee for ambiguous cases. On decentralized venues it is an optimistic
oracle with a dispute window.

Either way, the venue carries the settlement-integrity risk and the resolver carries
none of it. The resolver says "I followed the procedure." The venue has no artifact
that would let it, or its auditor, or its regulator, check that claim later.

This became a live regulatory question on **2026-06-10**, when the CFTC issued a
Notice of Proposed Rulemaking amending Rule 40.11. Among the grounds on which an
event contract may be deemed contrary to the public interest, the proposal names
**settlement integrity deficits — specifically a lack of clear and objective
resolution criteria.** Comments closed 2026-07-27.

At the same time the supply of regulated venues expanded: Payward closed its
**$550M acquisition of Bitnomial on 2026-05-04**, giving Kraken a Designated Contract
Market, a Derivatives Clearing Organization and a Futures Commission Merchant in one
transaction, with perpetual futures brought onshore through a 2026-05-29 filing.

"Clear and objective resolution criteria" is a property of a *declared procedure*.
Whether the procedure was *followed* on a given resolution is a separate, checkable
question. RAP-1 is a format for asking the second question mechanically.

**What this is not:** it is not a resolution oracle, not a data source, not a dispute
mechanism, and not a substitute for a venue's own review process. It sits beside
them and produces one artifact: a re-computable verdict on whether a submitted
evidence log is internally consistent with a declared procedure.

---

## 2. Model

Two documents in, one verdict out.

```
  declared_process   ──┐
  (what you said       ├──►  A1 … A7  ──►  verdict ∈ {conformant,
   you would do)       │                              indeterminate,
  run_evidence       ──┘                              non_conformant}
  (what you recorded
   yourself doing)
```

The verifier **never reads the resolver's systems** and never reads its own operator's
ledgers. The data direction is resolver → verifier. This is deliberate: a verifier
that attested to its own runs would have no third-party value.

Verification is **recomputation, not judgment**. There is no language model anywhere
in the path. The predicate grammar (§5) is closed and non-Turing-complete, evaluated
under a node budget, with no dynamic `eval` of any kind. Two parties running the same
inputs get the same verdict, and either can reproduce the other's.

### 2.1 Ternary verdicts

Every check returns one of four values, and the distinction between the last two is
the load-bearing design decision in this profile:

| Check verdict | Meaning |
|---|---|
| `pass` | the evidence affirmatively supports the claim |
| `fail` | the evidence **contradicts** the claim |
| `unverifiable` | the evidence **does not speak to** the claim |
| `not_applicable` | the declaration did not make this kind of claim at all |

`unverifiable` is never scored as `fail`. A resolver that omits timestamps has not
been caught doing anything wrong; it has produced a log that cannot answer a timeline
question. Collapsing those two states is how an attestation format becomes a
false-accusation machine, and an attestation may end up in front of the resolver's own
auditor or counsel.

### 2.2 Aggregation

```
any check fail          → non_conformant
else any unverifiable   → indeterminate
else                    → conformant
```

`not_applicable` checks are excluded from aggregation. So is a check the submitter
**opted out of entirely** — currently only A7 with zero signed records. Never
attempting a stronger guarantee is a normal minimal submission and must not drag an
otherwise-clean result down; *partially* attempting one is ambiguous and is scored
`indeterminate`. Zero evidence records never scores `conformant` by default.

**`indeterminate` is the expected verdict for a good-faith first submission.**
`conformant` requires the resolver to have instrumented for it in advance.

---

## 3. Input: `declared_process`

```jsonc
{
  "ordered": true,                       // optional; enables A2
  "window": {                            // optional; enables the A4 window test
    "start": "2026-09-05T00:00:00Z",
    "end":   "2026-09-05T23:59:59Z"
  },
  "forbidden": ["manual_override"],      // optional; enables A5
  "required": [                          // ≤ 200 entries, objects only
    {
      "id": "fetch_authoritative_source",
      "predicate": { /* §5 */ }          // optional; enables A3 for this step
    },
    { "id": "compare_to_contract_terms" },
    { "id": "publish_resolution" }
  ]
}
```

`required` steps **must be objects with an `id`**. The natural shorthand
`["fetch", "publish"]` is rejected at the boundary rather than silently promoted to
`{"id": "fetch"}` — a coerced step carries no predicate, and would then score as a
pass the submitter never actually declared.

## 4. Input: `run_evidence`

An ordered list of ≤ 4,000 record objects. Every field is optional; each one you
supply unlocks a check.

```jsonc
{
  "step_id": "fetch_authoritative_source",   // A1, A2, A3, A5
  "ts": "2026-09-05T14:02:11Z",              // A4  (ISO-8601)
  "fired": true,                             // A3  (or "outcome"; must be boolean)
  "predicate_inputs": {                      // A3
    "source_status": 200,
    "source_id": "bls.gov/cpi"
  },
  "prev_sha256": "…",                        // A6
  "signature": "…",                          // A7
  "sig_scheme": "hmac-sha256",               // A7
  "pubkey": "…"                              // A7
}
```

Where a step appears more than once, checks use its **first** occurrence.

---

## 5. Predicate grammar

A closed expression language over `predicate_inputs`. Four node types; anything else
evaluates to `unknown`.

```jsonc
{ "cmp": { "field": "source_status", "op": "eq", "value": 200 } }
{ "all": [ <node>, … ] }
{ "any": [ <node>, … ] }
{ "not": <node> }
```

Operators: `eq` `ne` `lt` `lte` `gt` `gte` `in` `contains`.

Evaluation is **Kleene three-valued**: `all` is `false` if any child is false,
`unknown` if any child is unknown, else `true`; `any` is `true` if any child is true,
`unknown` if any child is unknown, else `false`. A missing field, an unknown operator,
an empty `all`/`any` list, or a `TypeError` on comparison all yield `unknown` — never
a silent `false`.

Bounds: **2,000 nodes**, **depth 25**. Exceeding either yields `unknown`.

---

## 6. The seven checks

| ID | Name | `fail` when | `unverifiable` when | `not_applicable` when |
|---|---|---|---|---|
| **A1** | required steps present | a declared step has no evidence record | — | no required steps declared |
| **A2** | declared order honored | first-seen order of ≥2 declared steps contradicts declared order | — | `ordered` not set, or <2 orderable steps present |
| **A3** | predicate recomputation | a recomputed predicate contradicts the record's own `fired`/`outcome` | inputs missing, claim non-boolean, or predicate → `unknown` | no step declares a predicate |
| **A4** | timeline consistency | timestamps non-monotonic, or outside a declared `window` | some timestamps unparseable | fewer than 2 timestamped records |
| **A5** | forbidden steps absent | a declared-forbidden `step_id` appears | — | no forbidden steps declared |
| **A6** | evidence chain linkage | a `prev_sha256` does not match the recomputed hash | no record asserts `prev_sha256`, or only some do | exactly one record (nothing to chain) |
| **A7** | signature verification | a signature fails to verify | an unsupported `sig_scheme`, an unavailable verifier, or no signed records (opt-out) | — |

**A7 carries a `binding` field on `pass`**, mirrored at the top level as
`signature_binding`: `attributable` (ed25519), `tamper_evident_only` (hmac-sha256
or a mix), or `unsigned`. Two submissions can both score A7 `pass` and mean very
different things, so **never read the verdict without the binding.**

**A3 is the substantive one.** A1/A2/A5 check that the shape of the run matches the
shape of the declaration. A3 recomputes the resolver's *decision logic* from the
inputs it recorded and compares the result to what it claims it decided. A
`predicate_mismatch` means the resolver's own recorded inputs do not produce the
outcome it published — the closest thing in this profile to catching a wrong
resolution.

**A3 is also the check most likely to be `unverifiable` in practice**, because it
requires the resolver to have recorded `predicate_inputs` at the time, in the format
the declared predicate references.

---

## 7. The recipe

*This section is the artifact that was missing until this document. Without it, a
third party could verify an attestation but could not independently produce evidence
that would earn a `conformant` verdict, which made `conformant` a state only the
implementer could reach.*

### 7.1 Canonical JSON

Deterministic serialization, used for both hashing and signing:

- object keys sorted lexicographically
- **no whitespace** — no spaces after `:` or `,`
- `undefined` / absent keys dropped
- **every non-ASCII character escaped as `\uXXXX`** (astral characters as their
  UTF-16 surrogate pair, e.g. `😀` → `😀`)

> ⚠️ **The ASCII-escaping rule is load-bearing and easy to get wrong.** JavaScript's
> `JSON.stringify` does **not** escape non-ASCII; Python's `json.dumps` does, by
> default. A producer that emits `"café"` where the verifier hashes `"café"`
> computes a different digest, and **A6 and A7 both fail** on any record containing a
> single accented character or emoji. Version 1.0.0 of this document shipped a
> TypeScript reference with exactly that defect; it is corrected below.

Reference (TypeScript):

```ts
const NON_ASCII = new RegExp("[\\u0080-\\uFFFF]", "g");
const asciiEscape = (s: string): string =>
  s.replace(NON_ASCII, (c) => "\\u" + c.charCodeAt(0).toString(16).padStart(4, "0"));

const canonicalJson = (value: unknown): string => {
  if (value === null || typeof value !== "object") return asciiEscape(JSON.stringify(value));
  if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
  const obj = value as Record<string, unknown>;
  const keys = Object.keys(obj).filter((k) => obj[k] !== undefined).sort();
  return `{${keys.map((k) => `${asciiEscape(JSON.stringify(k))}:${canonicalJson(obj[k])}`).join(",")}}`;
};
```

Python equivalent: `json.dumps(obj, sort_keys=True, separators=(",", ":"))` — the
default `ensure_ascii=True` is the behaviour above; do **not** pass
`ensure_ascii=False`.

Both implementations above were run against the same inputs — including accented
characters and an astral emoji — and produce byte-identical output.

### 7.2 Chain linkage (A6)

For each record after the first:

```
prev_sha256 = sha256( canonical( record[i-1]  minus  {prev_sha256, signature, sig_scheme, pubkey} ) )
```

The four excluded keys are removed **before** canonicalization. Excluding them is what
makes the chain computable in a single forward pass: a record's hash cannot depend on
its own signature.

### 7.3 Signature (A7) — two schemes, two different guarantees

Both sign the same payload:

```
payload = canonical( record  minus  {signature, sig_scheme, pubkey} )
```

**`ed25519` — attributable. Use this when a counterparty must be able to say *who*
produced the evidence.**

```
signature  = ed25519_sign( private_key, payload )      -> 64 bytes, hex-encoded (128 chars)
pubkey     = raw ed25519 public key                    -> 32 bytes, hex-encoded (64 chars)
sig_scheme = "ed25519"
```

The public key cannot sign. A7 `pass` therefore binds the evidence to the holder of
the private key, and the verifier reports `binding: "attributable"`.

**`hmac-sha256` — tamper-evident only.**

```
signature  = HMAC-SHA256( key = pubkey_string_as_utf8_bytes, message = payload )
sig_scheme = "hmac-sha256"
```

Note the key is the **`pubkey` string itself, UTF-8 encoded** — not hex-decoded bytes.
Implementers must match this byte-for-byte or A7 will fail.

Because the verification key *is* the signing key and travels inside the record,
**anyone who can check the signature can also forge it.** A7 `pass` under this scheme
means the bundle has not been altered since assembly; it attributes nothing, and the
verifier reports `binding: "tamper_evident_only"`. A submission mixing both schemes is
reported at the weaker level.

Where records are produced inside a trusted execution environment, derive the key per
run so the underlying secret never leaves the enclave:

```
pubkey = sha256_hex( `${signing_key}:${run_id}` )       # hmac-sha256 only
```

An unknown or unsupported `sig_scheme` — and an `ed25519` verifier that is
unavailable on the machine running the check — yields `unverifiable`, **never**
`fail`. An absent verifier is a fact about that machine, not about the evidence.

### 7.4 Complete producer

```ts
import { sha256 } from "@noble/hashes/sha256.js";
import { hmac }   from "@noble/hashes/hmac.js";
import { ed25519 } from "@noble/curves/ed25519.js";
import { bytesToHex } from "@noble/hashes/utils.js";

const CHAIN_EXCLUDE = ["prev_sha256", "signature", "sig_scheme", "pubkey"];
const SIG_EXCLUDE   = ["signature", "sig_scheme", "pubkey"];

const canonicalBytes = (rec: Record<string, unknown>, exclude: string[]): Uint8Array => {
  const trimmed = Object.fromEntries(
    Object.entries(rec).filter(([k]) => !exclude.includes(k)));
  return new TextEncoder().encode(canonicalJson(trimmed));   // §7.1 — ASCII-escaped
};

/** scheme "ed25519": pass a 32-byte private key. scheme "hmac-sha256": pass a secret
 *  string, and the per-run key is derived from it. */
export function sealEvidence(records: Record<string, unknown>[],
                             opts: { scheme: "ed25519"; privateKey: Uint8Array }
                                 | { scheme: "hmac-sha256"; signingKey: string; runId: string }) {
  const pubkey = opts.scheme === "ed25519"
    ? bytesToHex(ed25519.getPublicKey(opts.privateKey))
    : bytesToHex(sha256(new TextEncoder().encode(`${opts.signingKey}:${opts.runId}`)));

  const out = records.map((r) => ({ ...r } as Record<string, unknown>));
  for (let i = 0; i < out.length; i++) {
    if (i > 0) {
      out[i].prev_sha256 = bytesToHex(sha256(canonicalBytes(out[i - 1], CHAIN_EXCLUDE)));
    }
    const payload = canonicalBytes(out[i], SIG_EXCLUDE);
    out[i].signature = opts.scheme === "ed25519"
      ? bytesToHex(ed25519.sign(payload, opts.privateKey))
      : bytesToHex(hmac(sha256, new TextEncoder().encode(pubkey), payload));
    out[i].sig_scheme = opts.scheme;
    out[i].pubkey     = pubkey;
  }
  return out;
}
```

### 7.5 Conformance vector

Self-test your implementation against this. With
`signing_key = "vault-secret-example"` and `run_id = "run-001"`:

```
pubkey = b803b4ca95c6ea3da426ca0c4be8bad67844c9c9ac1e499192f9b1e73626fee3
```

Sealing these three records —

```jsonc
[{"step_id":"fetch_authoritative_source","ts":"2026-09-05T14:02:11Z",
  "fired":true,"predicate_inputs":{"source_status":200}},
 {"step_id":"compare_to_contract_terms","ts":"2026-09-05T14:02:13Z"},
 {"step_id":"publish_resolution","ts":"2026-09-05T14:02:15Z"}]
```

— must produce exactly:

| # | `prev_sha256` | `signature` |
|---|---|---|
| 0 | *(none)* | `0c91ffa4b78b01f7e4d565d76cc26db64ac9457eda3a46454db6622cf5d9678e` |
| 1 | `8779deaceec478a7691921f59731ea1f8803857890840c03aef7c68e47e556c0` | `c1ce1ce7a1f985831cb5bf6bbe4aaf64dffadcedb967d95180c4dd9377c0c6ca` |
| 2 | `bbdad7533d68dda9767596094d3b44990d016468cfdcb12d1738bb0d95c70c74` | `a4fbce08d247c25d6735b7e1b969b770fc65339a5539e419fc02e486ebcab7ca` |

Submitted against the `declared_process` in §10 this yields **`conformant`** —
A1/A2/A3/A4/A6/A7 `pass`, A5 `not_applicable`, `signature_binding`
`tamper_evident_only`, `attestation_id` `2760826254c56fb5cf379ddbc1737d37`.

**ed25519 vector.** With the RFC 8032 seed
`9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bcc7cae0d8d0a` over the same
three records:

```
pubkey = 400237577b4006fc627b3fa445cecfae5700b711c85b52c8a110843d7004b0a7
```

| # | `signature` |
|---|---|
| 0 | `759453d78188a0a890e0560589de9dcdb656de0f81c2da41b5ae300c3bf011551dd567466d956f14467974efc7c2daec05bc7cea7292c652b4ca181fed522306` |
| 1 | `542cabdadb3c84591188103f352e7c809188ebbb43430e54de017aa9ba10260e4b41077d0e62519838bf659da247a6a768d97b8684cf5d0ac16c040f192a5f0f` |
| 2 | `c1eafd14dcca9c8b1d01e0fea28008ef578bfa3f99c82752201f30522c1c38bf4d947aafcad346bcfb2f455ec933c2004689865c8c994ff871fb62b5020c7501` |

`prev_sha256` values are **identical to the table above** — the chain hash excludes
all signature fields, so it does not depend on the scheme. Verdict: `conformant`,
`signature_binding` **`attributable`**, `attestation_id`
`96112955d196a0b82337a20ae030b059`.

**Negative controls.** All four must hold; an implementation that passes any of them
has a broken canonicalization and must not be used.

| Mutation | Required result |
|---|---|
| edit any field without re-sealing | `non_conformant`, A6 `fail`, A7 `fail` |
| replace a signature with zeros | `non_conformant`, A7 `fail` |
| sign with a different ed25519 key | `non_conformant`, A7 `fail` |
| malformed key/signature hex or wrong length | `indeterminate`, A7 `unverifiable` — **never** `fail` |

*These vectors were produced by an implementation written from §7.1–§7.4 alone, in a
different language from the reference, and checked against the live verifier; the
corrected §7.1 canonicalization was separately cross-checked between TypeScript and
Python on accented and astral input. That round-trip is what makes §7 publishable
rather than merely descriptive.*

---

## 8. ERC-8004 binding

RAP-1 works with no identity layer at all. Binding it to an ERC-8004 passport answers
a different question — *which resolver* produced this attestation, and what is its
history — and is what makes a series of attestations a track record rather than a pile
of unrelated documents.

| Question | Answered by |
|---|---|
| Did the resolver follow its declared procedure on this resolution? | RAP-1 verdict + `attestation_id` |
| Which resolver? | ERC-8004 passport, resolved from the caller's key |
| Has it done this before, and how did those go? | the passport's accumulated attestation history |
| Can an outside party check any of it without asking us? | the recipe in §7 + the published check definitions in §6 |

`attestation_id` is `sha256(canonical({checks, verdict}))[:32]` — content-addressed, so
the same inputs always produce the same id, and an id that does not reproduce is
evidence the artifact was altered.

**The passport is the addressable, portable half; the attestation is the per-event
half.** Neither is useful to a venue without the other: an identity with no record of
conduct is a name, and a record of conduct with no identity cannot be attributed.

### 8.1 Why attestations are not written to a validation registry

They are off-chain JSON artifacts. Content-addressing (above) makes one tamper-evident
once you hold it; it does not make it **discoverable** or **third-party-timestamped**.

The obvious home for that is ERC-8004's validation registry — *and it does not exist
yet.* Checked 2026-09-05 against the registry contracts curated by the 8004 team: they
publish `IdentityRegistry` and `ReputationRegistry` addresses across 40+ chains, and
**no `ValidationRegistry` address on any of them**, noting that this portion of the
spec is *"still under active update and discussion with the TEE community"* and will be
revised in a follow-up spec update later this year.

On Base mainnet (chain 8453) the two live registries are:

| Registry | Address | Status |
|---|---|---|
| Identity | `0x8004A169FB4a3325136EB29fA0ceB6D2e539a432` | live (BlindOracle is agent `60979`) |
| Reputation | `0x8004BAa17C55a88189AE136b182e5fdA19dE9b63` | live |
| **Validation** | — | **not deployed, spec in flux** |

So this is a blocked dependency, not an omission. Two workable interim paths exist and
neither is implemented here:

1. **Reputation registry.** `giveFeedback(...)` takes a trailing `bytes32`, which could
   carry an `attestation_id`. This overloads a reputation primitive to carry a
   validation artifact — expedient, and arguably a misuse that would need unwinding once
   the real registry lands.
2. **Independent Merkle anchoring.** Batch attestation ids into a root and anchor that
   to Base, which is what BlindOracle's existing proof-anchoring rail already does for
   other proof kinds. Chain-agnostic and survives the spec settling, at the cost of
   being our own scheme rather than a standard one.

**Adopt neither on the strength of this document.** Until the validation registry ships,
an attestation's discoverability is whatever the parties agree to out of band, and this
profile says so rather than implying an on-chain guarantee it does not provide.

## 9. Conformance levels

Levels describe **what the evidence supports**, so a resolver can state its posture
without over-claiming.

| Level | Requires | Meaning |
|---|---|---|
| **RAP-1-BASIC** | `verdict != non_conformant`, A1 `pass` | steps declared and observed |
| **RAP-1-LOGIC** | BASIC + A3 `pass` | recorded inputs reproduce the published outcome |
| **RAP-1-CHAINED** | LOGIC + A6 `pass` | evidence is hash-linked and order-fixed |
| **RAP-1-SEALED** | CHAINED + A7 `pass` under `hmac-sha256` | bundle is tamper-evident, **not attributable** — the verification key travels in the record and can also sign |
| **RAP-1-ATTRIBUTED** | CHAINED + A7 `pass` under `ed25519` (`signature_binding: attributable`) | evidence is additionally bound to the holder of a private key |

There is deliberately **no level that asserts the resolution was correct.** No
combination of these checks can support that claim: the evidence is submitter-supplied
(a fabricated but consistent log scores identically), and the predicate grammar cannot
express judgment-based criteria at all.

---

## 10. Calling it

```bash
curl -s -X POST https://api.craigmbrown.com/v1/services/security.process-attestation \
  -H 'Content-Type: application/json' \
  -d '{
    "declared_process": {
      "ordered": true,
      "required": [
        {"id": "fetch_authoritative_source",
         "predicate": {"cmp": {"field": "source_status", "op": "eq", "value": 200}}},
        {"id": "compare_to_contract_terms"},
        {"id": "publish_resolution"}
      ]
    },
    "run_evidence": [
      {"step_id": "fetch_authoritative_source", "ts": "2026-09-05T14:02:11Z",
       "fired": true, "predicate_inputs": {"source_status": 200}},
      {"step_id": "compare_to_contract_terms",  "ts": "2026-09-05T14:02:13Z"},
      {"step_id": "publish_resolution",         "ts": "2026-09-05T14:02:15Z"}
    ]
  }'
```

Priced at $0.25/call over x402/USDC on Base. Malformed input returns a structured
error envelope distinguishing `validation` (the submission) from `internal` (a defect
in the verifier) — a verifier bug must never be reported as the submitter's fault.

The handler never raises: an unexpected fault returns an `internal` error rather than
an HTTP 500 the caller paid for.

---

## 11. Prior evidence

A production Chainlink CRE workflow produced hash-chained, HMAC-signed evidence inside
a trusted execution environment and submitted it to this verifier on **2026-09-03**.
Attestation `24a3f06d…` returned **conformant** — A1 pass, A2 pass, A4 pass, A6 pass,
A7 pass; 0 fail, 0 unverifiable; A3 and A5 `not_applicable` (no predicates or forbidden
steps declared). The per-run signing key was derived from a vault secret inside the
enclave, so the secret never left it.

That run is the existence proof that the format is producible under real constraints.
It is **not** evidence that the format proves what a venue needs, for the reasons in
the evidence remains submitter-supplied, and that run used the symmetric scheme, so it
is tamper-evident rather than attributable.

---

## 12. Changelog

| Version | Date | Change |
|---|---|---|
| 1.0.0 | 2026-09-05 | First publication. Recipe published. |
| 1.1.0 | 2026-09-05 | **`ed25519` scheme added** — A7 can now attribute evidence to a keyholder, reported as `signature_binding`. **Canonicalization defect in the v1.0.0 TypeScript reference corrected** (§7.1): it did not ASCII-escape non-ASCII characters, so any record containing an accented character or emoji failed A6/A7 against the verifier. Conformance vectors updated. |

## 13. References

- CFTC, *Prediction Markets* NPRM (Rule 40.11), published 2026-03-16, [Federal Register 2026-05105](https://www.federalregister.gov/documents/2026/03/16/2026-05105/prediction-markets) · [CFTC](https://www.cftc.gov/LawRegulation/FederalRegister/proposedrules/2026-05105.html)
- Greenberg Traurig, [*CFTC Proposes New Rules for Event Contracts on Prediction Markets*](https://www.gtlaw.com/en/insights/2026/6/cftc-proposes-new-rules-for-events-contracts-on-prediction-markets) (June 2026)
- Norton Rose Fulbright, [*CFTC advances regulatory framework for prediction markets*](https://www.nortonrosefulbright.com/en/knowledge/publications/fed865b0/cftc-advances-regulatory-framework-for-prediction-markets)
- Congressional Research Service, [*CFTC Issues Proposed Rule Regarding Prediction Markets*](https://www.congress.gov/crs-product/LSB11441)
- CoinDesk, [*Kraken parent Payward closes $550 million Bitnomial deal, securing full CFTC derivatives stack*](https://www.coindesk.com/business/2026/05/04/kraken-parent-payward-closes-usd550-million-bitnomial-deal-securing-full-cftc-derivatives-stack) (2026-05-04)
- The TRADE, [*Kraken launches first CFTC-regulated perpetual futures for US traders*](https://www.thetradenews.com/kraken-launches-first-cftc-regulated-perpetual-futures-for-us-traders/)
- Track360, [*Prediction Market Oracles & Resolution Guide 2026*](https://track360.io/blog/prediction-market-oracles-resolution-settlement-operator-guide-2026) (resolution models; Kalshi Outcome Review Committee)
- ERC-8004 — Trustless Agents (identity / reputation / validation registries)
