FlexTrust Trust Receipt — specification v1

Normative, public (ADR-016). Served at /spec/trust-receipts. A trust receipt is FlexTrust’s signed attestation of a SIGNER’s published standing at a stated time, bound to the digest of a message the signer signed. It attests the signer, never the content, and it is an assessment, not a guarantee.

1. What a receipt says

“On commitment day D, the agent identified as subject — controlling the key signer — held FlexTrust score S (band B, confidence C, evidence tier E) under methodology version V, and that score is leaf P of the daily Merkle commitment whose root is R. The signer signed a message whose digest is M.”

FlexTrust signs exactly these facts as EIP-712 typed data. Nothing the presenting agent writes — no “purpose”, no free text — is under the FlexTrust signature.

2. Roles

  • Signer (presenter): an agent that signs a message (an LOI, a bid, a delivery promise — any commitment) with its own key and buys a receipt for it. FlexTrust never holds keys.
  • FlexTrust (attester): verifies the signer’s signature, resolves the signer to its credit file, takes the COMMITTED score for the day, countersigns the attestation, publishes the verification key.
  • Verifier (counterparty): any agent or human holding the artifact — a bank officer reading a signed LOI, an escrow contract, a marketplace. Verification is free and needs no FlexTrust account.

3. EIP-712 typed data

Domain: { name: "FlexTrust", version: "1", chainId: 8453 } (no verifyingContract in v1; a future on-chain verifier ships as version “2”).

Primary type ScoreAttestation:

Field Type Meaning
receiptId string UUID v4 of the receipt
subject string canonical DID of the scored agent (erc8004:<chain>:<token>, eoa:0x…, anchor:<kind>:<fp>)
subjectId uint64 FlexTrust agent id (needed to recompute the Merkle leaf)
signer string evm:0x<lowercase address> or ed25519:<64 hex>
messageDigest bytes32 digest of what the signer signed (see §4)
digestScheme string eip191, eip712 or ed25519-sha256
score uint16 committed score, 0–1000
band string presentation band (low, emerging, established, high)
confidenceBps uint16 confidence × 10,000
evidenceTier string settled, reputation, attested, identity_only
methodologyVersion string e.g. v1.2
commitmentDay string ISO date of the daily commitment
commitmentRoot bytes32 0x + the day’s Merkle root (sha256 hex)
leafPosition uint32 the subject’s leaf position that day
issuedAt uint64 unix seconds
validUntil uint64 unix seconds — commitment time + 24 h (the day-frozen period)
keyId string id of the FlexTrust attestation key used

Deliberately excluded: percentile (drifts daily), exposure-dependent risk bands and flags, any presenter-supplied text.

4. The message digest

Scheme Signer kind messageDigest Signer signature
eip191 evm keccak256("\x19Ethereum Signed Message:\n" ‖ len ‖ message) personal_sign, 65 bytes
eip712 evm the typed-data hash the agent signed 65 bytes
ed25519-sha256 ed25519 sha256(message) 64 bytes

FlexTrust stores the digest only, never the message. Issuers may submit the message (eip191, ed25519) or just the digest (eip712, or eip191 without disclosing the text).

5. The artifact

JSON, self-contained:

schema, receipt_id, url,
attestation       — the 17 fields above, exactly as typed
domain, types, primary_type
issuer            — { name, well_known, address, key_id }
attestation_signature — 0x + 65 bytes (r‖s‖v)
signer_signature  — as submitted
proof             — { leaf: {agent_id, value, computed_at, salt}, leaf_format, position, siblings[], pairing, root, committed_on }
status            — valid | expired | revoked   (unsigned, informational)
disclaimer
verify            — { human, json, commitments, well_known }

Compact form for footers and QR codes: FTR1. + base64url(JSON without types, verify, disclaimer, primary_type).

6. Verification (offline, without FlexTrust)

  1. Window: validUntil > now. A receipt speaks for its commitment day only.
  2. Signer: recover/verify signer_signature over messageDigest per digestScheme; it must equal attestation.signer. If you hold the message, recompute the digest from it and compare.
  3. Attestation: hash attestation as EIP-712 with the domain and types; ecrecover attestation_signature; the address must appear in /.well-known/flextrust.jsonattestation_keys[] with the same key_id, and issuedAt must fall inside that key’s validity (before any compromised_at).
  4. Merkle: leaf = sha256(agent_id ‖ "|" ‖ value ‖ "|" ‖ computed_at ‖ "|" ‖ salt); fold siblings from position (even index → sha256(node ‖ sibling), odd → sha256(sibling ‖ node), odd node at a level pairs with itself); the result must equal commitmentRoot, and leaf.value must equal score, leaf.agent_id must equal subjectId, position must equal leafPosition.
  5. Root: the root for commitmentDay in GET /commitments.json?day=… (or your own copy of the daily roots) must equal commitmentRoot.
  6. Revocation (online, optional): GET /api/v1/trust_receipts/<id> returns revoked_at and live flags an offline check cannot see (revoked registration, owner suspension, open disputes, a confirmed manipulation pattern).

Steps 1–5 need nothing from FlexTrust beyond two public, cacheable documents. FlexTrust’s own verifier implements exactly these steps.

7. Issuance

POST /api/v1/trust_receipts — paid like a score query (API key unit or x402), body:

{ "signer": { "kind": "evm" | "ed25519", "identifier": "0x…" | "<hex/base64 pubkey>" },
  "scheme": "eip191" | "eip712" | "ed25519-sha256",
  "signature": "…",
  "message": "…"            // eip191 / ed25519 (optional for eip191)
  "signed_digest": "0x…"    // eip712, or eip191 without the message
}

Responses: 201 the artifact · 422 unknown signer / signature does not verify · 409 the subject is not in a daily commitment yet, or today’s commitment is stale · 503 FlexTrust is not issuing (attestation key not configured). MCP twins: issue_trust_receipt (one unit), verify_trust_receipt (free).

The signer must already be known to FlexTrust: a registration owner address, a shadow (eoa:) agent, or an anchored key (POST /api/v1/anchors, free).

8. Keys and rotation

The attestation key is secp256k1, held only in FlexTrust’s encrypted credentials, and published as an address with a key_id and validity window at /.well-known/flextrust.json. Rotation adds a new key and retires the old one with retired_at; receipts issued under a retired key stay verifiable. A compromised key carries compromised_at; verifiers reject receipts with issuedAt at or after it, and FlexTrust revokes them. The EIP-712 domain separates attestations from anything else the key could sign.

9. What a receipt is not

Not a guarantee of performance, not an endorsement of the signed content, not custody, not an identity assertion beyond “this key controls this file”, not a substitute for an online check where revocation matters. The disclaimer travels with the artifact.